Tema 8: Genericidad.
Genericidad.
-
La genericidad no es una propiedad esencial para que un lenguaje sea considerado OO.
-
Podríamos decir que la genericidad es otro tipo de polimorfismo.
-
Recordad lo que decíamos en las primeras prácticas cuando hablabamos de
List
yStack
:typedef char Element; // Hasta que veamos genericidad usaremos este typedef.Element
era el tipo de los elementos almacenados en listas y pilas.
-
Estaría bien disponer de una característica del lenguaje que nos permitiera hacer esto no solo para un tipo, sino para todos los tipos que queramos.
-
De este modo podríamos crear simultáneamente listas y/o pilas de caracteres, de enteros, de droides, etc…
Genericidad y C++.
-
C++ la incorpora mediante el concepto de plantilla (template) y nos permite aplicar la idea tanto a clases como a funciones.
-
Un template o plantilla es un mecanismo que permite al programador usar tipos como parámetros para una clase o una función.
-
Veamos un ejemplo muy sencillo aplicado a funciones, imaginad que queremos crear una función que intercambie dos datos del mismo tipo, p.e. enteros, cadenas o droides:
// [1] // [2]void swap (int& a , int& b) { | void swap (string& a , string& b) {int t = a; | string t = a;a = b; | a = b;b = t; | b = t;} | }// [3]void swap (Droid& a , Droid& b) {Droid t = a;a = b;b = t;} -
¿Cómo son todas las funciones anteriores?
-
¿Y si hubiera una forma de escribir el código una sola vez parametrizando el tipo de dato con el que trabajan?
Plantillas de funciones.
-
La hay, mediante el uso de templates o plantillas de funciones, mira:
// Así declaramos una plantilla de una función.// La función todavía no existe.template <class T> // Podemos emplear typename en lugar de classvoid swap (T& a , T& b) {T t = a;a = b;b = t;} -
Para que la función exista hay que instanciar su plantilla, p.e.:
int s, t;string m, n;...swap (s, t); // swap (int&, int&)swap (m, n); // swap (string&, string&) -
Para poder instanciar la plantilla de una función genérica tenemos que tener accesible el código fuente de su declaración.
-
Es por eso que el código de implementación de la plantilla de una función o una clase suele ir en ficheros de cabecera: ‘.h’
Plantillas de clases.
-
De forma similar a como hemos hecho para funciones podemos hacerlo para clases.
-
Si hacemos genérica la clase
Node
empleada enTree
:template <typename T>class Node { // El tipo de los elementos es Tpublic:Node (T e);...private:T _key;Tree lefts, rights;}; -
La instanciamos así:
Node<char> nc; // nc variable de tipo Node de caracterNode<Droid> nd; // nd variable de tipo Node de Droid -
¿ Y si Tree también es genérica ?
-
Entonces la clase Node anterior quedaría así:
template <typename T>class Node { // El tipo de los elementos es Tpublic:Node (T e);...private:T _key;Tree<T> lefts, rights; // ¿Declaración o instanciación?};
Parámetros de un template.
-
La mayoría de lenguajes que tienen genericidad sólo permiten usar como argumentos de la plantilla tipos.
-
C++ permite usar otras cosas, p.e. enteros o cadenas.
template <typename T, int N> struct Array {T elements[N];...};Array<char,12> ca; // Array de 12 caracteresArray<Droid, 512> da; // Array de 512 droides -
Se permiten valores por defecto:
template <typename T = int, int N = 100> struct Array {T elements[N];...};Array<> ca; // Array<int, 100>Array<Droid> da; // Array<Droid, 100>Array<Array<char, 120>> aa; // Matriz de 100x120 caracteresEn C++ previo al estándar de 2011 los ángulos de cierre dobles (\(>>\)) de un template deben estar separados, no pueden ir juntos: \(> .. >\).
Un ejemplo más completo.
// -- The C++ programming language, 4th. ed.////////////////////////// Contenido del '.h' //////////////////////////template<typename T>struct Link { Link* pre; Link* suc; T val;};
template<typename T>class List { Link<T>* head;public: List() : head(nullptr) { } List(const T& t) : head (new Link<T>{0,0,t} ) { } ... void print_all() const;};
/////////////////////////// Contenido del '.cc' ///////////////////////////
// Implementación del método "List<T>::print_all() const"template<typename T> void List<T>::print_all() const { for (Link<T>* p = head; p; p = p −> suc) //p depende de T cout << *p;}
Programación genérica.
-
Como podrás imaginar esta característica abre las puertas a un nuevo modo de crear programas.
-
Ya no solo debes pensar en el código que escribes, sino a qué dará lugar cuando se instancie. Requiere llevar bastante más cuidado.
-
A principios de la década de los 90 Alexander Stepanov comenzó a desarrollar lo que por aquel entonces se denominó la biblioteca STL y que hoy en día forma parte de la biblioteca estándar de C++.
-
En esencia STL consistía en una colección de contenedores, iteradores, algoritmos y funciones programados bajo el paradigma de la genericidad.
-
Obligó a los fabricantes de compiladores a incorporar todo lo que demandaba este nuevo estilo de programación. En cierto modo, STL se convirtió en el código de testeo de muchos fabricantes de compiladores de C++.
-
La genericidad que proporciona C++ está años luz a la genericidad que podemos encontrar en otros lenguajes (C#, Java, etc…) donde básicamente consiste en un preprocesador algo más elaborado.
La biblioteca estándar de C++.
-
El lenguaje C++ no es sólo el compilador.
-
Es también su biblioteca estándar, gran parte de la cual está, escrita usando genericidad (STL).
-
En ella nos encontramos, junto a otros componentes, con una serie de clases que implementan TADS que representan contenedores (listas, pilas, colas, etc…).
-
Para usar cualquier componente de esta biblioteca debemos incluir un archivo de cabecera que contiene el código que lo implementa. A continuación (si es un elemento genérico) debemos proceder a instanciarlo, p.e:
#include <list>#include <queue>std::list<int> il; // Lista de enterosstd::list<Droid> dl; // Lista de droidesstd::list<std::queue<char>> ql; // Lista de colas de caracteresstd::queue<std::list<char>> lq; // Cola de listas de caracteres -
Dispones de más información sobre esta biblioteca aquí.
Ejemplo de uso de std::vector.
// Compilar con std=c++11#include <iostream>#include <vector>
class Robot { public: static int rn; const static Robot zero;
Robot(std::string n, float bl = 1.0) { theName = n; theBattLevel = bl; rn++; }
friend std::ostream& operator<< (std::ostream& os, const Robot& r);
virtual ~Robot() {}
private: std::string theName; float theBattLevel;};
std::ostream&operator<< (std::ostream& os, const Robot& r) { os << "-----------------\n"; os << " Name: " << r.theName << '\n'; os << " BL: " << r.theBattLevel<< '\n'; os << "-----------------\n";
return os;}
int Robot::rn = 0;const Robot Robot::zero = Robot ("", 0.0);
int main(int argc, char *argv[]){ std::vector<Robot*> rv;
for (int i = 0; i < 10; i++) rv.push_back (new Robot ("robot-" + std::to_string(Robot::rn), static_cast<float>(.1*i)));
std::cout << "\nBuilt robots: " << Robot::rn << "\n\n";
for (int i = 0; i < 10; i++) std::cout << *rv[i] << '\n'; // ¿porqué *rv[i] y no rv[i]?
return 0;}
Aclaraciones.
- Este contenido no es la bibliografía completa de la asignatura, por lo tanto debes estudiar, aclarar y ampliar los conceptos que en ellas encuentres empleando los enlaces web y bibliografía recomendada que puedes consultar en la página web de la ficha de la asignatura y en la web propia de la asignatura.