Hola, a todos sean bienvenidos a mi nuevo post. En el dia de hoy hablaremos sobre plantillas o «tipos parametrizados», estas son de una gran utilidad por eso empecemos con el post.
Que son las plantillas? Una plantilla es un medio para enseñarle al compilador como crear una lista de cualquier tipo en lugar de crear un conjunto de listas de tipos especificos. Cuando nosotros creamos un tipo especifico desde una plantilla se llama instanciacion (no suena muy lindo pero se llama asi) y las clases individuales se llaman instancia de una planilla.Veamos como se define una plantilla a traves del siguiente ejemplo:
# include <iostream>
using namespace std;
const int TamanioPredet = 10;
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo(){ delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T &operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
int obtenerTamanio() { return suTamanio; }
private:
T * apTipo;
int suTamanio;
};
Observemos este caso, antes de definir la clase template (plantilla), definimos por medio de la palabra reservada template el nombre de nuestra plantilla, en este caso T, la sintaxis para crearla es como se ve resaltado en el ejemplo:
template < class elNombre >
Luego se declara la clase como siempre para nuestro caso veamos estas dos lineas:
En la seccion public: T &operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; } En la seccion private: T * apTipo;
En el primer caso pueden observar como en lugar utilizar un objeto se utiliza el tipo creado por la plantilla, esto es porque se espera recibir una referencia a un objeto del arreglo (array) y en el segundo caso, se prepara para regresar una referencia al tipo creado en la planilla (es decir T). como se ve en el ejemplo anterior nosotros utilizaremos la palabra Arreglo de la forma simple como se ve en el codigo pero para utilizarla en el resto del programa deberemos utilizar Arreglo< T >, esto identificara la procedencia de la clase Arreglo, Un metodo comun es trabajar a la clase y sus funciones como una sola declaracion antes de convertirla en plantilla, para entender un poco mejor este concepto pasemos a ver un ejemplo:
template00.cpp
# include <iostream>
using namespace std;
const int TamanioPredet = 10;
class Animal
{
public:
Animal(int);
Animal();
~Animal(){}
int ObtenerPeso() const { return suPeso; }
void Desplegar() const { cout << suPeso; }
private:
int suPeso;
};
Animal::Animal(int peso):
suPeso(peso)
{}
Animal::Animal(): suPeso(0) {}
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo() { delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T & operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
const T &operator[](int desplazamiento) const
{ return apTipo[ desplazamiento ]; }
int ObtenerTamanio() const { return suTamanio; }
private:
T * apTipo;
int suTamanio;
};
template < class T >
Arreglo< T >::Arreglo(int tamanio):
suTamanio(tamanio)
{
apTipo = new T[ tamanio ];
for(int i = 0; i < tamanio; i++)
apTipo[ i ] = 0;
}
template < class T >
Arreglo< T >::Arreglo(const Arreglo & rhs)
{
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
}
template < class T >
Arreglo< T > & Arreglo< T >::operator=(const Arreglo & rhs)
{
if (this == &rhs)
return *this;
delete [] apTipo;
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
return *this;
}
int main()
{
Arreglo< int > elArreglo;
Arreglo< Animal > elZoologico;
Animal *apAnimal;
for(int i = 0; i < elArreglo.ObtenerTamanio(); i++)
{
elArreglo[ i ] = i * 2;
apAnimal = new Animal(i * 3);
elZoologico[ i ] = *apAnimal;
}
for(int j = 0; j < elArreglo.ObtenerTamanio(); j++ )
{
cout << "elArreglo[" << j << "]:\t";
cout << elArreglo[ j ] << "\t\t";
cout << "elZoologico[" << j << "]:\t";
elZoologico[ j ].Desplegar();
cout << endl;
}
return 0;
}
Primero vamos a crear una clase llamada Animal, definimos sus constructores, luego pueden ver como definimos la plantilla y el parametro a utilizar:
template < class T >
En este caso le asignamos el nombre de T, luego definiremos la clase a a transformar en plantilla, en este caso Arreglo, observen como dijimos antes, el operador [] y el apuntador son del tipo del template, despues el resto es como todas las clases que vinimos viendo hasta ahora, ahora veamos como se define un constructor, para observar la definicion de los mismos por fuera de la clase, miren el siguiente bloque:
template < class T >
Arreglo< T >::Arreglo(int tamanio):
suTamanio(tamanio)
{
apTipo = new T[ tamanio ];
for(int i = 0; i < tamanio; i++)
apTipo[ i ] = 0;
}
Primero siempre se debe decir a cual template pertenece, es de igual forma al declarar la clase template, luego definimos la clase del template (en este caso Arreglo) y luego entre los simbolos de menor que (<) y mayor que (>) ingresamos unicamente el nombre del tipo de plantilla creada, en este caso T, despues siguen el simbolo de direccionamiento (::) para luego ir a la funcion o metodo a definir, en este caso es el primer constructor de la clase utilizada como plantilla, donde creara el aparcamiento de un apuntador del tipo T llamado apTipo y luego procedera a llenarlo de ceros porque simplemente es para crear el aparcamiento, y de esta forma es como se define un metodo por fuera de la clase. En los siguientes bloques si observan ocurre exactamente lo mismo, primero definimos el template, luego va la clase del template con el nombre del tipo, seguido por el nombre del metodo a definir, si no desean declararlo por afuera pueden hacerlo como se hizo con el destructor en la clase donde ya se definio en el mismo:
~Arreglo() { delete [] apTipo; }
Despues en el main(), primero crearemos un objeto del tipo Arreglo (array) del tipo entero llamado elArreglo, y luego crearemos otro objeto del tipo Arreglo pero esta vez del tipo Animal llamado elZoologico, por ultimo crearemos un apuntador del tipo Animal llamado apAnimal, en el primer bucle for llenaremos los respectivos arreglos y en el siguiente bloque los mostraremos en pantalla, esta es la salida de nuestro programa:
tinchicus@dbn001dsk:~/lenguaje/c++$ ./template00 elArreglo[0]: 0 elZoologico[0]: 0 elArreglo[1]: 2 elZoologico[1]: 3 elArreglo[2]: 4 elZoologico[2]: 6 elArreglo[3]: 6 elZoologico[3]: 9 elArreglo[4]: 8 elZoologico[4]: 12 elArreglo[5]: 10 elZoologico[5]: 15 elArreglo[6]: 12 elZoologico[6]: 18 elArreglo[7]: 14 elZoologico[7]: 21 elArreglo[8]: 16 elZoologico[8]: 24 elArreglo[9]: 18 elZoologico[9]: 27 tinchicus@dbn001dsk:~/lenguaje/c++$
La forma de definicion de las plantillas vista en el main() tambien puede ser utilizada para las funciones, veamos algunos ejemplos:
- void mifuncion(Arreglo< int > &), esta es una definicion correcta.
- void mifuncion(Arreglo< T > &), esta es incorrecta porque no existe una definicion de que tipo es T.
- void mifuncion(Arreglo &), esta tambien es incorrecta por ser una plantilla y no una clase.
Recuerden el metodo mas general utilizado es a traves de la declaracion de una plantilla antes de la funcion, como vimos en el codigo, en este caso:
template < class T >
void mifuncion(Arreglo< T > &);
Este caso es correcto porque al estar definida la plantilla con la primer linea le decimos al programa que debe tomar el valor de la planilla T y en la siguiente linea ya sabe a quien pertenece T a diferencia de los ejemplos anteriores donde al no estar declarada previamente toma a T como una clase. ahora veamos como se puede usar instancias en las funciones como se ve en las siguientes lineas:
template < class T >
void mifuncion(Arreglo< T > &, Arreglo < int > &);
En este caso tambien es correcto porque al estar declarada la plantilla es valido el primer argumento, y esto no afecta al segundo argumento donde Arreglo sera del tipo entero. Nuestra siguiente parada sera sobre plantillas y funciones amigas, vamos a tener tres formas distintas de funciones amigas:
- Clases y funciones amigas no perteneciente a la plantilla.
- Clases y funciones amigas de la plantilla general.
- Clases y funciones amigas de plantilla de tipo especificos.
Vamos a ver el primer tipo de plantilla y funciones amigas con el siguiente ejemplo:
template01.cpp
# include <iostream>
using namespace std;
const int TamanioPredet = 10;
class Animal
{
public:
Animal(int);
Animal();
~Animal(){}
int ObtenerPeso() const { return suPeso; }
void Desplegar() const { cout << suPeso; }
private:
int suPeso;
};
Animal::Animal(int peso):
suPeso(peso)
{}
Animal::Animal(): suPeso(0) {}
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo() { delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T & operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
const T &operator[](int desplazamiento) const
{ return apTipo[ desplazamiento ]; }
int ObtenerTamanio() const { return suTamanio; }
friend void Intruso(Arreglo < int >);
private:
T * apTipo;
int suTamanio;
};
void Intruso(Arreglo < int > elArreglo)
{
cout << "\n ++++ Intruso ++++ \n";
for(int i = 0; i < elArreglo.suTamanio; i++)
cout << "i: " << elArreglo.apTipo[ i ] << endl;
cout << "\n";
}
template < class T >
Arreglo< T >::Arreglo(int tamanio):
suTamanio(tamanio)
{
apTipo = new T[ tamanio ];
for(int i = 0; i < tamanio; i++)
apTipo[ i ] = 0;
}
template < class T >
Arreglo< T >::Arreglo(const Arreglo & rhs)
{
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
}
template < class T >
Arreglo< T > & Arreglo< T >::operator=(const Arreglo & rhs)
{
if (this == &rhs)
return *this;
delete [] apTipo;
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
return *this;
}
int main()
{
Arreglo< int > elArreglo;
Arreglo< Animal > elZoologico;
Animal *apAnimal;
for(int i = 0; i < elArreglo.ObtenerTamanio(); i++)
{
elArreglo[ i ] = i * 2;
apAnimal = new Animal(i * 3);
elZoologico[ i ] = *apAnimal;
}
for(int j = 0; j < elArreglo.ObtenerTamanio(); j++ )
{
cout << "elZoologico[" << j << "]:\t";
elZoologico[ j ].Desplegar();
cout << endl;
}
cout << "Usemos la funcion amiga para encontrar los ";
cout << " miembros de Arreglo< int >";
Intruso(elArreglo);
cout << "Listo.\n";
return 0;
}
Es similar al ejemplo anterior pero la diferencia va a estar en la nueva linea dentro del template, esta es:
friend void Intruso(Arreglo < int >);
Esta linea nos permitira tener acceso a los elementos almacenados en el private de la clase Arreglo a traves del metodo Intruso(), como este es el prototipo luego debemos definirlo. Al ser una funcion «amiga» no es necesario declarar si pertenece o no al template pero como no es una plantilla solamente puede manejar los tipos enteros, la longitud del bucle for en este metodo estara definido por el valor de suTamanio, luego en el for mostraremos el valor almacenado en cada posicion de apTipo, tambien almacenado en la parte privada del template, despues los siguiente bloques son exactamente igual al caso anterior, la unica diferencia va a estar en main(), donde mostraremos los datos almacenados en el objeto elZoologico y despues le diremos que por medio de Intruso() muestren los valores privados, dando como salida esto:
tinchicus@dbn001dsk:~/lenguaje/c++$ ./template001 elZoologico[0]: 0 elZoologico[1]: 3 elZoologico[2]: 6 elZoologico[3]: 9 elZoologico[4]: 12 elZoologico[5]: 15 elZoologico[6]: 18 elZoologico[7]: 21 elZoologico[8]: 24 elZoologico[9]: 27 Usemos la funcion amiga para encontrar los miembros de Arreglo< int > ++++ Intruso ++++ i: 0 i: 2 i: 4 i: 6 i: 8 i: 10 i: 12 i: 14 i: 16 i: 18 Listo. tinchicus@dbn001dsk:~/lenguaje/c++$
Como pueden ver a traves de una funcion «amiga», en este caso Intruso(), cada vez que es generada una nueva instancia de arreglos de tipo entero, la planilla le permite acceso a los datos privados de la misma, ahora vamos a ver el segundo caso, es decir las clases y funciones de plantilla general por medio del siguiente ejemplo:
template02.cpp
# include <iostream>
using namespace std;
const int TamanioPredet = 10;
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo() { delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T & operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
const T &operator[](int desplazamiento) const
{ return apTipo[ desplazamiento ]; }
int ObtenerTamanio() const { return suTamanio; }
friend ostream & operator<< (ostream & salida, Arreglo< T > & elArreglo)
{
for(int i = 0; i < elArreglo.ObtenerTamanio(); i++)
salida << "[ " << i << " ] " << elArreglo[ i ] << endl;
return salida;
}
private:
T * apTipo;
int suTamanio;
};
template < class T >
Arreglo< T >::Arreglo(int tamanio):
suTamanio(tamanio)
{
apTipo = new T[ tamanio ];
for(int i = 0; i < tamanio; i++)
apTipo[ i ] = 0;
}
template < class T >
Arreglo< T >::Arreglo(const Arreglo & rhs)
{
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
}
template < class T >
Arreglo< T > & Arreglo< T >::operator=(const Arreglo & rhs)
{
if (this == &rhs)
return *this;
delete [] apTipo;
suTamanio = rhs.ObtenerTamanio();
apTipo = new T[ suTamanio ];
for(int i = 0; i < suTamanio; i++)
apTipo[ i ] = rhs[ i ];
return *this;
}
int main()
{
bool Detener = false;
int desplazamiento, valor;
Arreglo< int > elArreglo;
while(!Detener)
{
cout << "Escriba un desplazamiento [0-9] ";
cout << " y un valor. (-1 para Detener): ";
cin >> desplazamiento >> valor;
if (desplazamiento < 0)
break;
if (desplazamiento > 9)
{
cout << "++++ Utilice valores entre 0 y 9++++\n";
continue;
}
elArreglo[ desplazamiento ] = valor;
}
cout << "Aca 'ta el arreglo completo:\n";
cout << elArreglo << endl;
return 0;
}
En este caso, tenemos un codigo muy parecido al anterior pero en este caso utilizaremos al operador << como «amigo», esto lo agregaremos dentro del template para ser utilizado, veamos el bloque del template:
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo() { delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T & operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
const T &operator[](int desplazamiento) const
{ return apTipo[ desplazamiento ]; }
int ObtenerTamanio() const { return suTamanio; }
friend ostream & operator<< (ostream & salida, Arreglo< T > & elArreglo)
{
for(int i = 0; i < elArreglo.ObtenerTamanio(); i++)
salida << "[ " << i << " ] " << elArreglo[ i ] << endl;
return salida;
}
private:
T * apTipo;
int suTamanio;
};
En este bloque tenemos la plantilla y hablaremos sobre funcion del operador << como «amiga» de la plantilla, esta se encargara basicamente por cada Arreglo creado en base a esta planilla tendra asignada automaticamente este operador, luego sera igual salvo el main() donde tendremos un bucle while() el cual se encargara de recibir dos tipos de datos, uno de desplazamiento y otro de valor, saldra unicamente si ingresamos un valor por debajo de cero, y si ingresamos un valor mayor a 9 no los notificara en pantalla, una vez terminado el while() nos mostrara en pantalla todos los valores ingresados, les muestro una salida:
tinchicus@dbn001dsk:~/lenguaje/c++$ ./template02 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 1 3 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 9 1 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 2 4 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 3 5 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 4 6 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 5 11 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 7 22 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 8 7 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 0 8 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): 6 10 Escriba un desplazamiento [0-9] y un valor. (-1 para Detener): -1 0 Aca 'ta el arreglo completo: [ 0 ] 8 [ 1 ] 3 [ 2 ] 4 [ 3 ] 5 [ 4 ] 6 [ 5 ] 11 [ 6 ] 10 [ 7 ] 22 [ 8 ] 7 [ 9 ] 1 tinchicus@dbn001dsk:~/lenguaje/c++$
Observen como en base a la posicion informada en el desplazamiento ubica en la posicion correcta del array al valor, ahora vamos a ver otro ejemplo de como declarar y definirlo a nuestro operador:
template < class T >
class Arreglo
{
public:
Arreglo(int suTamanio = TamanioPredet);
Arreglo(const Arreglo & rhs);
~Arreglo() { delete [] apTipo; }
Arreglo &operator= (const Arreglo &);
T & operator[] (int desplazamiento) { return apTipo[ desplazamiento ]; }
const T &operator[](int desplazamiento) const
{ return apTipo[ desplazamiento ]; }
int ObtenerTamanio() const { return suTamanio; }
friend ostream & operator<< (ostream & salida, Arreglo< T > & elArreglo);
private:
T * apTipo;
int suTamanio;
};
template < class T >
ostream & operator<< (ostream & salida, Arreglo< T > & elArreglo)
{
for(int i = 0; i < elArreglo.ObtenerTamanio(); i++)
salida << "[ " << i << " ] " << elArreglo[ i ] << endl;
return salida;
}
Esta es la forma original como vi el codigo fuente, en el template creaban el prototipo y despues externamente definen la funcion pero en mi caso nunca me anduvo con modificaciones y buscando por internet no pude solucionar la falla y en el mejor de los casos solo me tiraba este error de compilacion:
tinchicus@dbn001dsk:~/lenguaje/c++$ g++ template02a.cpp -o template02a
template02a.cpp:19:57: warning: friend declaration ‘std::ostream& operator<<(std::ostream&, Arreglo&)’ declares a non-template function [-Wnon-template-friend]
friend ostream & operator << (ostream &, Arreglo< T > &);
^
template02a.cpp:19:57: note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)
/tmp/ccesuWSf.o: En la función main': template02a.cpp:(.text+0x117): referencia a
operator<<(std::ostream&, Arreglo&)' sin definir
collect2: error: ld returned 1 exit status
tinchicus@dbn001dsk:~/lenguaje/c++$
Por esto, opte en hacer lo mostrado en el codigo final pero en teoria si funcionara tendria un resultado similar. Hasta aqui hemos visto una pequeña introduccion sobre templates, para que sirven y algunas implementaciones. Entre ellas:
- Como crear un template.
- Como implementar y para que sirve un template
- Tres opciones de plantillas
- Utilizarla con funciones amigas.
Espero les haya sido util sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.
Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.


Tambien podes donar
Es para mantenimiento del sitio, gracias!
$1.50