Вы находитесь на странице: 1из 9

Las plantillas en C++

Junio 2014
Introduccin
Ventajas
Inconvenientes
Cundo utilizar plantillas?
Qu debo poner en las .hpp y .cpp
Convencin de notaciones
Algunas plantillas conocidas
STL
BGL
Trabajar con plantillas
Especificaciones de plantillas
Plantilla por defecto
Obtener los parmetros de plantilla, tipos y mtodos estticos de una clase-plantilla
Templates recurrentes
Testear valores de tipo plantilla
Enlaces utiles

Introduccin
Las plantillas (Templates) son uno de los grandes aportes de C++ al lenguaje C. Hasta antes de
las plantillas, se pasaban variables como parmetros de las funciones. Gracias al concepto de
plantilla, es posible pasar tipos como parmetros, y de este modo definir funciones genricas.
Pero el concepto de plantillas no se limita a las funciones, tambin puede ser utilizado en clases
y estructuras.

Ventajas
El inters de las plantillas residen en: -la generalizacin: desde el momento en que el tipo
incluye todo lo que es utilizado en la funcin o clase-plantilla, es posible pasar cualquier tipo
como parmetro. -simplicidad: nicamente se codifica una funcin o clase sin importar el tipo
pasado como parmetro, lo que hace que el mantenimiento del cdigo sea ms fcil.

Inconvenientes
-Como lo veremos a continuacin, el uso de plantillas requiere tomar algunas precauciones

(typename) -El programa demora ms en ser compilado.

Cundo utilizar plantillas?


El uso de plantillas es apropiado para definir contenedores, es decir estructuras que sirven para
almacenar una coleccin de objetos (una lista, un vector, un grafo) Las plantillas tambin son
apropiadas para definir algoritmos genricos que se aplican a una familia de clase. Por ejemplo,
es interesante codificar un algoritmo del camino mas corto, independientemente de la estructura
del grafo. El uso de un functor puede ser apropiado para acceder a los pesos instalados sobre
los arcos del grafo en este caso. La clase de grafo pasada como parmetro debe cumplir un
cierto nmero de pre-requisitos para que el algoritmo pueda ser aplicado. Si no, el programa no
compilar

Qu debo poner en las .hpp y .cpp


Al ser C++ un lenguaje compilado, est claro que no podemos compilar para una funcin o clase
dada todas sus versiones. Por ejemplo, si definimos una clase-plantilla de vector, que
llamaremos my_vector<T>, no podemos compilar my_vector<int>, my_vector<char>,
my_vector<my_struct> ...sabiendo que hay una infinidad de tipos pudiendo ser pasados como
parmetros. Por ello, una clase-plantilla es (re)compilada para cada tipo de instancia presente
en el programa. As, si en mi programa utilizo my_vector<int> y my_vector<char>, nicamente
estas versiones sern compiladas. Si en otro programa utilizo my_vector<my_vector<double> >,
compilar nicamente my_vector<float> y my_vector<my_vector<float> >. Lo que debemos
retener, es que el compilador se las arregla solo para saber qu versin debe compilar. De lo
que acabamos de decir, se deduce que una funcin o clase-plantilla no puede ser
precompilado, ya que es compilado para cada instancia. Por lo tanto retendremos la regla
siguiente: Si una funcin o clase-plantilla es utilizada nicamente en un .cpp (archivo fuente),
entonces puede ser implementada en este .cpp. Si no, debe ser implementada en un .hpp
(encabezado). Observacin: Puede ocurrir que un archivo conteniendo una clase-plantilla
tenga una extensin diferente a la de encabezado (.h o .hpp), por ejemplo .tcc. Esto es
nicamente una convencin. Personalmente, yo las considero como encabezados.

Convencin de notaciones
Los parmetros de plantillas son generalmente escritos en maysculas (mientras que los otros
tipos son generalmente escritos en minscula). En la prctica, podemos escribirlos como
queramos. Personalmente los escribo precedidos de una T (por ejemplo Tgraph para designar
un parmetro de plantilla que representa un grafo). Esto puede parecer intil pero veremos que
es muy prctico con los typenames ya que vuelve el cdigo ms legible.

Algunas plantillas conocidas


STL
La STL (Standard Template Library) viene por defecto con los compiladores C++. Esta biblioteca

incluye un juego de contenedores genricos, especialmente: std::vector: vectores (tabla de


elementos de tipo T adyacentes en memoria), acceso en O(1). std::set: conjuntos de elementos
de tipo T sin repeticiones y ordenados segn el operador <, acceso en O(log(n)) std::list: listas
encadenadas (acceso en O(n), insercin al inicio y al final de la lista en O(1)) Para hacerse una
idea del contenido de la STL, hacer clic en el siguiente enlace: http://www.sgi.com/tech/stl/

BGL
La BGL (Boost Graph Library) proporciona clases de grafo genricos y los algoritmos
correspondientes (algoritmo del camino ms corto, algoritmo de flot). Para hacerse una idea
del contenido de la BGL, hacer clic en el siguiente enlace.
http://www.boost.org/doc/libs/1_35_0/libs/graph/doc/table_of_contents.html Esta no esta
presente por defecto pero se instala fcilmente. Por ejemplo bajo Debian:
aptitude install libboost-graph-dev

Trabajar con plantillas


Para trabajar con plantillas, necesitamos 4 cosas: -La palabra clave typename: indica que el tipo
que sigue es abstracto (parmetro de plantilla o depende de un parmetro de plantilla) y debe
ser tomado en cuenta nicamente cuando se le instancia. -La palabra clave template: indica que
la clase o funcin que le sigue toma parmetros de plantilla. Despus de la palabra clave
template se escriben directamente los parmetros de plantilla (precedidos de la palabra clave
typename, struct, class, o tipo de base segn el tipo de parmetro de plantilla esperado) entre
<< >>, seguidos de la clase o funcin. En el ejemplo que sigue veremos: -cmo codificar una
clase-plantilla -cmo codificar una funcin-plantilla -cmo codificar un operador-plantilla En este
ejemplo las clases o funciones-plantilla toman solo un parmetro de plantilla, pero el
procedimiento ser el mismo con varios parmetros de plantilla. Ejemplo:
template <typename T1, typename T2, ... >
type_devuelve_t mi_funcion(param1_t p1,param2_t p2, ...){
...
}
template <typename T1, typename T2, ... >
class mi_clase_t{
...
};
template <typename T1, typename T2, ... >
struct mi_struct_t{
...
};
-El operador :: : permite acceder a los campos (en particular los tipos) y mtodos estticos de
una clase o de una estructura. No es especifico a las plantillas (se aplica a las clases y
estructuras en general y a los namespaces). Es un poco como el / de los directorios. As,

std::vector<int>::const_iterator significa que accedo al tipo const_iterator, almacenado en la


clase vector<int>, ella misma codificada en el namespace std. Vamos a definir nuestra propia
clase de vector a fin de ilustrar lo que acabamos de decir. Evidentemente en la practica
utilizaremos directamente la clase std::vector de la STL...
#include <iostream>
//----------------------- inicio my_vector_t.hpp
#include <cstdlib>
#include <ostream>
// Una clase-plantilla que toma un parmetro
template <typename T>
class my_vector_t{
protected:
unsigned tamao; // almacena el tamao del vector
T *data; // almacena los componentes del vector
public:
// El constructor
my_vector_t(unsigned tamao0 = 0,const T & x0 = T()):
tamao(tamao0),
data((T *)malloc(sizeof(T)*tamao0))
{
for(unsigned i=0;i<taille;<gras>i) data[i] = x0;
}
// El destructor
~my_vector_t(){
free(data);
}
// Devuelve el tamao del vector
inline unsigned size() const{
return tamao;
}
// Un accesador en solo lectura sobre la iesima casilla del vector
inline const T & operator[](unsigned i) const{
if(i >= size()) throw;
return data[i];
}
// Un accesador en lectura escritura sobre la iesima casilla del vector
inline T & operator[](unsigned i){
if(i >= size()) throw;
return data[i];
}
};

// Un operador-plantilla
template <typename T>
std::ostream & operator<<(std::ostream & out,const my_vector_t<T> & v){
unsigned n = v.size();
out << "[ ";
for(unsigned i=0;i<n;<gras>i) out << v[i] << ' ';
out << ']';
return out;
}
//----------------------- fin my_vector_t.hpp
// Una funcin-plantilla
template <typename T>
void escribir(const my_vector_t<T> & v){
unsigned n = v.size();
std::cout << "[ ";
for(unsigned i=0;i<n;<gras>i) std::cout << v[i] << ' ';
std::cout << ']';
}
int main(){
my_vector_t<int> v(5); // un vector de 5 enteros
v[0] = 6; v[1] = 2; v[2] = 3; v[3] = 4; v[4] = 8;
escribir<int>(v); // llamado a la funcin template
std::cout << std::endl;
escribir(v); // llamado implcito de escribir<int>
std::cout << std::endl;
std::cout << v << std::endl; // llamado al operador template
return 0;
}
Al ejecutar:
[62348]
[62348]
[62348]
Todo lo que est entre inicio clase my_vector_t" y "fin clase my_vector_t" podra ser desplazado
a un encabezado (por ejemplo my_vector.hpp) luego ser incluido por el programa principal.

Especificaciones de plantillas
Nada impide que se implemente especficamente una clase o funcin para un conjunto de
parmetros de plantilla. Notemos que no es necesario especificar todos los parmetros de
plantilla. Partiendo del ejemplo precedente:

#include "my_vector.hpp"
// Una especificacin de plantilla
void escribir(const my_vector_t<int> & v){
unsigned n = v.size();
std::cout << "{ ";
for(unsigned i=0;i<n;<gras>i) std::cout << v[i] << ' ';
std::cout << '}';
}
int main(){
my_vector_t<int> v(5); // un vector de 5 enteros
v[0] = 6; v[1] = 2; v[2] = 3; v[3] = 4; v[4] = 8;
escribir<int>(v); // invoca a la funcin template
std::cout << std::endl;
escribir(v); // invoca a escribir (prevalece a la invocacin implcita de escribir<int>)
std::cout << std::endl;
std::cout << v << std::endl; // invoca el operador template
return 0;
}
Al ejecutarlo:
[62348]
{62348}
[62348]

Plantilla por defecto


Tambin es posible precisar un parmetro de plantilla por defecto del mismo modo que con un
parmetro de funcin. Por ejemplo:
template<typename T = int>
class my_vector_t{
//...
};
int main(){
my_vector<> v; // un vector de int
return 0;
}
Algunos ejemplos conocidos de plantillas por defecto: en la STL el functor de comparacin,
utilizado en las std::set, es inicializado por defecto por std::less (functor de comparacin basado
en <). De este modo podemos escribir indistintamente:

std::set<int> s;
std::set<int,std::less<int> > s_;

Obtener los parmetros de plantilla, tipos y mtodos


estticos de una clase-plantilla
Una buena idea con las clases-plantilla, es poner typedef (en public) para poder obtener
fcilmente los parmetros de plantilla. Ejemplo: tenemos una clase c1 <T> y deseamos obtener
el tipo T. Esto ser posible gracias a typedef y typename.
template <typename T>
struct my_class_t{
typedef T data_t;
};
int main(){
typedef my_vector_t<int>::data_t data_t; // Esto es un entero
}
Sin embargo nicamente podemos aplicar el operador :: si un miembro de la izquierda no es un
tipo abstracto (dependiente de un tipo plantilla aun no evaluado). Por ejemplo, si deseamos
manipular el typedef "const_iterator" de la clase std::vector proporcionada por la STL, si los
parmetros de plantilla de std::vector no son asignados, el programa rechazar la compilacin.
void escribir(const std::vector<int> & v){
std::vector<int>::const_iterator vit(v.begin(),vend(v.end());
for(;vit!=vend;<gras>vit) std::cout << *vit << ' ';
}
template <typename T>
void escribir(const std::vector<int> & v){
std::vector<T>::const_iterator vit(v.begin(),vend(v.end()); // ERROR !
for(;vit!=vend;<gras>vit) std::cout << *vit << ' ';
}
Aqu std::vector<T> est situado a la izquierda de :: y depende de una parmetro de plantilla. Es
aqu que typename entra en juego.
template <typename T>
void escribir(const std::vector<int> & v){
typename std::vector<T>::const_iterator vit(v.begin(),vend(v.end());
for(;vit!=vend;<gras>vit) std::cout << *vit << ' ';
}
Debemos recordar que cuando el tipo a la izquierda de un :: depende de una parmetro de
plantilla, debe ser precedido de un typename. Debido a que los tipos rpidamente se hacen
difcil de manipular, es buena idea hacer typedef. En otro ejemplo, ms complicado, esto da por
ejemplo:

typedef typename std::vector<typename std::vector<T>::const_iterator>


::const_iterator mi_tipo_extrao_t

Templates recurrentes
Es posible definir plantillas recurrentes. Un ejemplo:
#include <iostream>
template <int N>
int fact(){
return N*fact<N-1>();
}
template <>
int fact<0>(){
return 1;
}
int main(){
std::cout << fact<5>() << std::endl;
return 0;
}
Aqu el inters es bastante moderado ya que concretamente se compila fact<5>, fact<4>...
fact<0>, es justo para dar un ejemplo simple de template recurrente.

Testear valores de tipo plantilla


Con boost es posible verificar si un tipo plantilla corresponde a un tipo esperado y bloquear la
compilacin si es as. Debido a que se utiliza la biblioteca boost, dar solo un ejemplo breve:
#include <boost/type_traits/is_same.hpp>
#include <boost/static_assert.hpp>
template <typename T>
struct mi_struct_que_debe_compilar_solo_si_T_es_int{
BOOST_STATIC_ASSERT((boost::is_same<T,int>::value));
//...
};

Enlaces utiles
Presentacin de la STL (Standard Template Library): ofrece numerosos contenedores de

plantilla: http://www.sgi.com/tech/stl/ [ Instroduccion a la STL en C<gras>] Presentacin de la


BGL (Boost Graph Library): una parte de la biblioteca boost, ofrece clases y algoritmos de grafos
de plantillas: http://www.boost.org/doc/libs/1_35_0/libs/graph/doc/table_of_contents.html PD: El
artculo original fue escrito por mamiemando, contribuidor de CommentCaMarche
Este documento intitulado Las plantillas en C++ de Kioskea (es.kioskea.net) esta puesto a diposicin bajo la
licencia Creative Commons. Puede copiar, modificar bajo las condiciones puestas por la licencia, siempre que esta
nota sea visible.

Вам также может понравиться