Vamos a ver un ejemplo completo de cómo aplicar la plantilla anterior a diferentes tipos.
Fichero de cabecera que declara y define la plantilla Tabla:
// Tabla.h: definición de la plantilla tabla: // C con Clase: Marzo de 2002 #ifndef T_TABLA #define T_TABLA template <class T> class Tabla { public: Tabla(int nElem); ~Tabla(); T& operator[](int indice) { return pT[indice]; } const int NElementos() { return nElementos; } 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; } #endif
Fichero de aplicación de plantilla:
// Tabla.cpp: ejemplo de Tabla // C con Clase: Marzo de 2002 #include <iostream> #include "Tabla.h" using namespace std; cont int nElementos = 10; int main() { Tabla<int> TablaInt(nElementos); Tabla<float> TablaFloat(nElementos); for(int i = 0; i < nElementos; i++) TablaInt[i] = nElementos-i; for(int i = 0; i < nElementos; i++) TablaFloat[i] = 1/(1+i); for(int i = 0; i < nElementos; i++) { cout << "TablaInt[" << i << "] = " << TablaInt[i] << endl; cout << "TablaFloat[" << i << "] = " << TablaFloat[i] << endl; } cin.get(); return 0; }
Ahora bien, supongamos que quieres usar la plantilla Tabla para crear una tabla de cadenas. Lo primero que se nos ocurre hacer probablemente sea:
Tabla<char*> TablaCad(15);
No hay nada que objetar, todo funciona, el programa compila, y no hay ningún error, pero... es probable que no funcione como esperas. Veamos otro ejemplo:
Fichero de aplicación de plantilla:
// Tablacad.cpp: ejemplo de Tabla con cadenas // C con Clase: Marzo de 2002 #include <iostream> #include <cstring> #include <cstdio> #include "Tabla.h" using namespace std; const int nElementos = 5; int main() { Tabla<char *> TablaCad(nElementos); char cadena[20]; for(int i = 0; i < nElementos; i++) { sprintf(cadena, "Numero: %5d", i); TablaCad[i] = cadena; } strcpy(cadena, "Modificada"); for(int i = 0; i < nElementos; i++) cout << "TablaCad[" << i << "] = " << TablaCad[i] << endl; cin.get(); return 0; }
Si has compilado el programa y has visto la salida, tal vez te sorprenda algo el resultado: Efectivamente, parece que nuestra tabla no es capaz de almacenar cadenas, o al menos no más de una cadena. La cosa sería aún más grave si la cadena auxiliar fuera liberada, por ejemplo porque se tratase de una variable local de una función, o porque se tratase de memoria dinámica.
TablaCad[0] = Modificada TablaCad[1] = Modificada TablaCad[2] = Modificada TablaCad[3] = Modificada TablaCad[4] = Modificada
¿Cuál es el problema?. Lo que pasa es que nuestra tabla no es de cadenas, sino de punteros a char. De hecho eso es lo que hemos escrito Tabla<char *>, por lo tanto, no hay nada sorprendente en el resultado. Pero esto nos plantea un problema: ¿cómo nos las apañamos para crear una tabla de cadenas?.
La solución es usar una clase que encapsule las cadenas y crear una tabla de objetos de esa clase.
Veamos una clase básica para manejar cadenas:
// CCadena.h: Fichero de cabecera de definición de cadenas // C con Clase: Marzo de 2002 #ifndef CCADENA #define CCADENA #include <cstring> using std::strcpy; using std::strlen; class Cadena { public: Cadena(char *cad) { cadena = new char[strlen(cad)+1]; strcpy(cadena, cad); } Cadena() : cadena(NULL) {} Cadena(const Cadena &c) : cadena(NULL) {*this = c;} ~Cadena() { if(cadena) delete[] cadena; } Cadena &operator=(const Cadena &c) { if(this != &c) { if(cadena) delete[] cadena; if(c.cadena) { cadena = new char[strlen(c.cadena)+1]; strcpy(cadena, c.cadena); } else cadena = NULL; } return *this; } const char* Lee() const {return cadena;} private: char *cadena; }; ostream& operator<<(ostream &os, const Cadena& cad) { os << cad.Lee(); return os; } #endif
Usando esta clase para cadenas podemos crear una tabla de cadenas usando nuestra plantilla:
// Tabla.cpp: ejemplo de Tabla de cadenas // C con Clase: Marzo de 2002 #include <iostream> #include <cstdio> #include "Tabla.h" #include "CCadena.h" using namespace std; #define nElementos = 5; int main() { Tabla<Cadena> TablaCad(nElementos); char cadena[20]; for(int i = 0; i < nElementos; i++) { sprintf(cadena, "Numero: %2d", i); TablaCad[i] = cadena; } strcpy(cadena, "Modificada"); for(int i = 0; i < nElementos; i++) cout << "TablaCad[" << i << "] = " << TablaCad[i] << endl; cin.get(); return 0; }
La salida de este programa es:
TablaCad[0] = Numero: 0 TablaCad[1] = Numero: 1 TablaCad[2] = Numero: 2 TablaCad[3] = Numero: 3 TablaCad[4] = Numero: 4
Ahora funciona como es debido.
El problema es parecido al que surgía con el constructor copia en clases que usaban memoria dinámica. El funcionamiento es correcto, pero el resultado no siempre es el esperado. Como norma general, cuando apliquemos plantillas, debemos usar clases con constructores sin parámetros, y tener cuidado cuando las apliquemos a tipos que impliquen punteros o memoria dinámica.
© Marzo de 2002 Salvador Pozo, salvador@conclase.net