TEMA 8: GENERICIDAD.
Contenidos
1. 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…
2. 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?
2.1. 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 class void 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'
2.2. 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 T public: Node (T e); ... private: T _key; Tree lefts, rights; };
La instanciamos así:
Node<char> nc; // nc variable de tipo Node de caracter Node<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 T public: Node (T e); ... private: T _key; Tree<T> lefts, rights; // ¿Declaración o instanciación? };
2.3. 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 caracteres Array<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 caracteres
En C++ previo al estándar de 2011 los ángulos de cierre dobles (\(>>\)) de un template deben estar separados, no pueden ir juntos: \(> .. >\).
2.4. 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; }
3. 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.
4. 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 enteros std::list<Droid> dl; // Lista de droides std::list<std::queue<char>> ql; // Lista de colas de caracteres std::queue<std::list<char>> lq; // Cola de listas de caracteres
- Dispones de más información sobre esta biblioteca aquí.
4.1. 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; }
5. Aclaraciones
- En ningún caso estas transparencias son la bibliografía 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.