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
<< < > >>

Ejemplo de uso de plantilla Tabla.  

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;
}

Posibles problemas:

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?.

Tablas 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.

<< < > >>