Curso de C++ v2.0
Consultas, lista de correo 'C++ Con Clase' 'C++ Con Clase' página de entrada Librerías estándar C Tabla de contenido Contactar con Webmaster
*Introducción
*1 Toma de contacto
*2 Variables I
*3 Funciones I: Declaración y definición
*4 Operadores I
*5 Sentencias
*6 Declaración de variables
*7 Normas para la notación
*8 Cadenas de caracteres
*9 Conversión de tipos
*10 Variables II: Arrays
*11 Variables III: Estructuras
*12 Variables IV: Punteros 1
*13 Operadores II: Más operadores
*14 Operadores III: Precedencia
*15 Funciones II: Parámetros por valor y referencia
*16 Variables V: Uniones
*17 Variables VI: Punteros 2
*18 Operadores IV: De bits y condicional
*19 Definición de tipos
*20 Funciones III
*21 Funciones IV: Sobrecarga
*22 Operadores V: Sobrecarga
*23 El preprocesador
*24 Funciones V: Recursividad
*25 Variables VII: Modificadores
*26 Espacios con nombre
*27 Clases I: Definiciones
*28 Declaración de clases
*29 Constructores
*30 Destructores
*31 El puntero this
*32 Sistema de protección
*33 Modificadores para miembros
*34 Más sobre funciones
*35 Operadores sobrecargados
*36 Herencia
*37 Funciones virtuales
*38 Derivación múltiple
*39 Trabajar con ficheros
*40 Plantillas
 . Sintaxis
 . Plantillas de funciones
 . Plantilla para Tabla
 . Ejemplo: plantilla Tabla
 . Tablas de cadenas
 . Plantillas como parámetros
 . Amigos de plantillas
 . Miembros estáticos
 . (Valores por defecto)
 . Ejemplo plantilla de pila
 . Librerías de plantillas
 . typename
*41 Punteros a miembros
*42 Castings
*43 Excepciones
*Ejemplos capítulos 1 a 6
*Ejemplos capítulos 8 y 9
*A Palabras reservadas C/C++
*B Trigrafos y símbolos alternativos
*C Librerías estándar
*D Streams
<< < > >>

40 Plantillas:

Según va aumentando la complejidad de nuestros programas y sobre todo, de los problemas a los que nos enfrentamos, descubrimos que tenemos que repetir una y otra vez las mismas estructuras.

Por ejemplo, a menudo tendremos que implementar arrays dinámicos para diferentes tipos de objetos, o listas dinámicas, pilas, colas, árboles, etc.

El código es similar siempre, pero estamos obligados a rescribir ciertas funciones que dependen del tipo o de la clase del objeto que se almacena.

Las plantillas (templates) nos permiten parametrizar estas clases para adaptarlas a cualquier tipo de dato.

Vamos a desarrollar un ejemplo sencillo de un array que pueda almacenar cualquier objeto. Aunque más adelante veremos que se presentan algunas limitaciones y veremos cómo solucionarlas.

Con lo que ya sabemos, podemos crear fácilmente una clase que encapsule un array de, por ejemplo, enteros. Veamos el código de esta clase:

// TablaInt.cpp: Clase para crear Tablas de enteros
// C con Clase: Marzo de 2002

#include <iostream>
using namespace std;

class TablaInt {
  public:
   TablaInt(int nElem);
   ~TablaInt();
   int& operator[](int indice) { return pInt[indice]; }

  private:
   int *pInt;
   int nElementos;
};

// Definición:
TablaInt::TablaInt(int nElem) : nElementos(nElem) {
   pInt = new int[nElementos];
}

TablaInt::~TablaInt() {
   delete[] pInt;
}

int main() {
   TablaInt TablaI(10);

   for(int i = 0; i < 10; i++)
      TablaI[i] = 10-i;

   for(int i = 0; i < 10; i++)
      cout << TablaI[i] << endl;

   cin.get();
   return 0;
}

Bien, la clase TablaInt nos permite crear arrays de la dimensión que queramos, para almacenar enteros. Quizás pienses que para eso no hace falta una clase, ya que podríamos haber declarado sencillamente:

   int TablaI[10];

Bueno, tal vez tengas razón, pero para empezar, esto es un ejemplo sencillo. Además, la clase TablaInt nos permite hacer cosas como esta:

   int elementos = 24;
   TablaInt TablaI(elementos);

Recordarás que no está permitido usar variables para indicar el tamaño de un array. Pero no sólo eso, en realidad esta podría ser una primera aproximación a una clase TablaInt que nos permitiría aumentar el número elementos o disminuirlo durante la ejecución, definir constructores copia, o sobrecargar operadores suma, resta, etc.

La clase para Tabla podría ser mucho más potente de lo que puede ser un array normal, pero dejaremos eso para otra ocasión.

Supongamos que ya tenemos esa maravillosa clase definida para enteros. ¿Qué pasa si ahora necesitamos definir esa clase para números en coma flotante?. Podemos cortar y pegar la definición y sustituir todas las referencias a int por float. Pero, ¿y si también necesitamos esta estructura para cadenas, complejos, o para la clase persona que implementamos en anteriores capítulos?, ¿haremos una versión para cada tipo para el que necesitemos una Tabla de estas características?.

Afortunadamente existen las plantillas y (aunque al principio no lo parezca), esto nos hace la vida más fácil.

Sintaxis.  

C++ permite crear plantillas de funciones y plantillas de clases.

La sintaxis para declarar una plantilla de función es parecida a la de cualquier otra función, pero se añade al principio una presentación de la clase que se usará como referencia en la plantilla:

template <class|typename <id>[,...]>
<tipo_retorno> <identificador>(<lista_de_parámetros>)
{
   // Declaración de función
};

La sintaxis para declarar una plantilla de clase es parecida a la de cualquier otra clase, pero se añade al principio una presentación de la clase que se usará como referencia en la plantilla:

template <class|typename <id>[,...]>
class <identificador_de_plantilla>
{
   // Declaración de funciones 
   // y datos miembro de la plantilla
};

Nota: La lista de clases que se incluye a continuación de la palabra reservada template se escriben entre las llaves "<" y ">", en este caso esos símbolos no indican que se debe introducir un literal, sino que deben escribirse, no me es posible mostrar estos símbolos en negrita, por eso los escribo en rojo. Siento si esto causa algún malentendido.

Pero seguro que se ve mejor con un ejemplo:

template <class T1>
class Tabla {
  public:
   Tabla();
   ~Tabla();
   ...
};

Del mismo modo, cuando tengamos que definir una función miembro fuera de la declaración de la clase, tendremos que incluir la parte del template y como nombre de la clase incluir la plantilla antes del operador de ámbito (::). Por ejemplo:

template <class T1>
Tabla<T1>::Tabla() {
  // Definición del constructor
}

Plantillas de funciones.  

Un ejemplo de plantilla de función puede ser esta que sustituye a la versión macro de max:

template <class T> 
T max(T x, T y) {
   return (x > y) ? x : y;
};

La ventaja de la versión en plantilla sobre la versión macro se manifiesta en cuanto a la seguridad de tipos. Por supuesto, podemos usar argumentos de cualquier tipo, no han de ser necesariamente clases. Pero cosas como estas darán error de compilación:

int a=2;
char b='j';

int c=max(a,b);

El motivo es que a y b no son del mismo tipo. Aquí no hay promoción implícita de tipos. Sin embargo, están permitidas todas las combinaciones en las que los argumentos sean del mismo tipo o clase, siempre y cuando que el operador > esté implementado para esa clase.

int a=3, b=5, c;
char f='a', g='k', h;
c = max(a,b);
h = max(f,g);

Plantilla para Tabla.  

Ahora estamos en disposición de crear una plantilla a partir de la clase Tabla que hemos definido antes. Esta vez podremos usar esa plantilla para definir Tablas de cualquier tipo de objeto.

template <class T>
class Tabla {
  public:
   Tabla(int nElem);
   ~Tabla();
   T& operator[](int indice) { return pT[indice]; }

  private:
   T *pT;
   int nElementos;
};

// Definición:
template <class T>
Tabla<T>::Tabla(int nElem) : nElementos(nElem) {
   pT = new T[nElementos];
}

template <class T>
Tabla<T>::~Tabla() {
   delete[] pT;
}

Dentro de la declaración y definición de la plantilla, podremos usar los parámetros que hemos especificado en la lista de parámetros del "template" como si se tratase de comodines. Más adelante, cuando creemos instancias de la plantilla para diferentes tipos, el compilador sustituirá esos comodines por los tipos que especifiquemos.

Y ya sólo nos queda por saber cómo declarar Tablas del tipo que queramos. La sintaxis es:

<identificador_de_plantilla><<tipo/clase>> <identificador/constructor>;

Seguro que se ve mejor con un ejemplo, veamos como declarar Tablas de enteros, punto flotante o valores booleanos:

Tabla<int>   TablaInt(32);   // Tabla de 32 enteros
Tabla<float> TablaFloat(12); // Tabla de 12 floats
Tabla<bool>  TablaBool(10);  // Tabla de 10 bools

Pero no es este el único modo de proceder. Las plantillas admiten varios parámetros, de modo que también podríamos haber especificado el número de elementos como un segundo parámetro de la plantilla:

template <class T, int nElementos>
class Tabla {
  public:
   Tabla();
   ~Tabla();
   T& operator[](int indice) { return pT[indice]; }

  private:
   T *pT;
};

// Definición:
template <class T, int nElementos>
Tabla<T,nElementos>::Tabla() {
   pT = new T[nElementos];
}

template <class T, int nElementos>
Tabla<T, nElementos>::~Tabla() {
   delete[] pT;
}

La declaración de tablas con esta versión difiere ligeramente:

Tabla<int,32>   TablaInt;   // Tabla de 32 enteros
Tabla<float,12> TablaFloat; // Tabla de 12 floats
Tabla<bool,10>  TablaBool;  // Tabla de 10 bools

Esta forma tiene una limitación: el argumento nElementos debe ser una constante, nunca una variable. Esto es porque el valor debe conocerse durante la compilación del programa. Las plantillas no son definiciones de clases, sino plantillas que se usan para generar las clases que a su vez se compilarán para crear el programa:

#define N 12
...
const n = 10;
int i = 23;

Tabla<int,N>     TablaInt;   // Legal, Tabla de 12 enteros
Tabla<float,3*n> TablaFloat; // Legal, Tabla de 30 floats
Tabla<char,i>    TablaFloat; // Ilegal
<< < > >>
Free Web Hosting