C/C++: plantillas (templates) en C++

Publicado por

En esta nota estudiaremos una característica genial de C++, se trata de las plantillas o templates. Una plantilla es una manera especial de escribir funciones y clases para que estas puedan ser usadas con cualquier tipo de dato, similar a la sobrecarga, en el caso de las funciones, pero evitando el trabajo de escribir cada versión de la función. Las ventajas son mayores en el caso de las clases, ya que no se permite hacer sobrecarga de ellas y tendríamos que decidirnos por una sola o hacer especializaciones usando la herencia. 

¿Cómo funcionan?

La magia de las plantillas está en no definir un tipo de dato desde el principio, sino dejar esto como algo pendiente y usar algo que permita manejar varias opciones, de hecho se usa una variable para este propósito. Veamos la sintaxis para el caso de las funciones:

// Para una función, ambas opciones son equivalentes
template <class identificador> definición_de_función;
template <typename identificador> definición_de_función;

El identificador es  el símbolo que guarda el tipo de dato que se ha de usar una vez elegido, por lo que en la definición de la función deberá utilizarse en lugar de los nombres de los tipos de datos, de esta manera queda hecha una función genérica a la cual podemos llamar función-plantilla.

Tal vez con un ejemplo esta idea quede más clara. Pensemos en una función que nos retorne el mayor de dos datos que le demos como argumentos y que sirva con cualquier tipo:

template <class tipo> 
tipo mayor(tipo dato1, tipo dato2){
  return (dato1 > dato2 ? dato1 : dato2);
}

El identificador que usamos es tipo”, por eso el tipo de dato de retorno y el de los parámetros debe ser ese identificador. Durante la compilación del programa, en la invocación a la función, se resuelve el tipo que se usará y el compilador escribirá, con base en la plantilla, una función que sirva con el tipo de dato resuelto y será la que realmente se utilice para dicha invocación.

Probemos nuestra función-plantilla y veamos lo que pasa:

int main(){
    int a = 1, b = 2, n; 
    float c = 1.0, d = 0.5, m; 
    
    n = mayor <int> (a,b); // Usando enteros
    m = mayor(c,d);        // Usando reales
    
    cout << "Entero mayor: " << n << endl;
    cout << "Real mayor: " << m << endl;
    
    return 0; 
}

Se puede usar una especificación explícita para el tipo de dato que se usará en la función escribiendo  <tipo_dato> o dejar que el compilador resuelva el tipo con los argumentos. Cabe destacar que las plantillas funcionan no solo con tipos de datos primitivos sino también con los estructurados como las clases, aunque para nuestro ejemplo debemos tener sobrecargado el operador de comparación usado en la definición de la función >”.

La función no sirve cuando los tipos de los datos son diferentes, entonces no podríamos usarla con un entero y un real, porque solamente se ha definido un identificador de tipo. Para usar más tipos basta con definir más identificadores, de esta manera podríamos escribir la función de la siguiente forma:

template <class tipo1, class tipo2> 
tipo1 mayor(tipo1 dato1, tipo2 dato2){
  return (dato1 > dato2 ? dato1 : dato2);
}

Con esta función es posible una llamada así:

int a = 10;
float b = 11.0;
float c = mayor(b,a); // como el primer argumento es un float, el resultado también lo es

Clases-plantilla

Como mencioné al principio de la nota, podemos escribir también clases con plantillas, llamadas clases-plantilla, y crear objetos que sirvan con cualquier tipo de dato, esta característica es de gran ayuda al momento de escribir, por ejemplo, una clase para arreglos o para matrices que sirven con cualquier tipo y no solo con uno.

En este caso, el identificador de tipo sirve en toda la definición de la clase, ya sea para los atributos o los métodos, a continuación tenemos la sintaxis para declarar una clase-plantilla:

template <class identificador> definición_de_clase;
// Para las funciones miembro definidas fuera de la clase
template <class identificador> 
tipo_retorno nombre_clase<identificador>::nombre_función(argumentos){
// implementación
}

Se entiende mejor la idea con un ejemplo, veamos algo simple:

template <class T> //identificador de tipo: T
class Coordenada{
private:
  T x; //atributos de tipo T
  T y;
  
public:
  Coordenada(T x=0, T y=0); //parámetros de tipo T
  T dameX(){return x}; 
  T dameY(){return y};      //retorna un tipo T
  void nuevoX(T x){this -> x = x};
  void nuevoY(T y){this -> y = y};
}

template <class T>
Coordenada<T>::Coordenada(T x, T y){
  this -> x = x;
  this -> y = y;
}

Con esta clase podemos instanciar objetos que representan coordenadas de cualquier tipo de dato, por ejemplo:

// Una coordenada de enteros
Coordenada <int> c1(1,2);
// Una coordenada de reales
Coordenada <float> c2(1.5, 0.5);

Para el caso de las clases, sí es necesario especificar explícitamente el tipo:

nombre_clase <tipo_dato> nombre_objeto(argumentos_constructor);

Programación genérica

Gracias a las clases-plantilla es posible lograr un estilo de programación genérico, como es el caso de la biblioteca estándar de plantillas STL (Standard Template Library) de C++ que utiliza las plantillas para crear y proporcionar una gran colección de herramientas como lo son los contenedores (vector es uno de los más conocidos) y algoritmos (como los de ordenamiento y de búsqueda) que trabajan sobre estas estructuras de datos. 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *