Los templates son algo tan sumamente complejo que en las universidades, si se da, es casi de refilón.
Es una herramienta muy potente... el problema es que no distingue entre amigos y enemigos y es bastante sencillo pegarse un tiro en el pie.
Una de las grandes virtudes de los teplates es que permiten realizar ciertos cálculos en tiempo de compilación, lo cual puede mejorar el rendimiento de la aplicación.
Por ejemplo, el clásico ejemplo de cálculo de factoriales. En primer lugar tenemos una función normal para calcular el factorial:
Código C++:
Ver originalint factorial(int n)
{
int toReturn=1;
if(n>1)
{
do
toReturn *= n;
while(--n);
}
return toReturn;
}
Ahora la versión con templates:
Código C++:
Ver originaltemplate<int T>
struct Factorial
{
enum { valor=T*Factorial<T-1>::valor };
};
template<>
struct Factorial<0>
{
enum { valor=1 };
};
La especialización Factorial<0> sirve para que el template normal termine las iteraciones en un momento dado y no empiece a multiplicar números negativos.
Y ahora vamos a poner un pequeño código que compare el tiempo de ejecución de ambas versiones:
Código C++:
Ver originalconst long MAX = 100000000;
int main(int argc, char *argv[])
{
QTime t;
t.start();
int acumulador = 0;
for( auto i=0L;i<MAX;i++)
acumulador += Factorial<10>::valor;
std::cout << "Template: " << t.elapsed() << " ms"<< std::endl;
t.start();
for( auto i=0L;i<MAX;i++)
acumulador += factorial(10);
std::cout << "Funcion: " << t.elapsed() << " ms" << std::endl;
// Esta línea la pongo para evitar que el compilador pueda hacer optimizaciones
// que eliminen el código de los bucles.
std::cout << acumulador << std::endl;
}
Por comodidad y por hacer el ejemplo rápido he usado QTimer que es una clase específica de Qt, pero podéis sustituirla por el control de tiempos que os de la gana.
Bueno, ya tenemos todo listo. Arrancamos en modo release y la salida por pantalla es la siguiente:
¿Por qué tanta diferencia? Al compilarse el template el compilador tiene que calcular el valor concreto del enum, así que no le queda más remedio que obtener el valor de Factorial<10> durante la compilación. Una vez que el programa entra en ejecución, como el valor ya ha sido calculado, recuperarlo no cuesta absolutamente nada de tiempo, mientras que la opción clásica se ve obligada a calcular el valor cada vez que se lo pidan.
Mejoras de C++11
A partir del estándar de C++11 se puede usar el modificador
constexpr en determinadas funciones. Este modificador viene a decir que ante una misma entrada la función SIEMPRE devolverá la misma salida. Esta característica permite que el compilador realice ciertas optimizaciones en beneficio del rendimiento. Eso sí, para poder usar este modificador hay que cumplir una serie de restricciones que no viene a cuento enumerarlas ahora mismo.
En este caso la función podría quedar así:
Código C++:
Ver originalconstexpr int factorial(int n)
{
return (n>1)? n*factorial(n-1) : 1;
}
Ejecutamos de nuevo el código y ahora los tiempos cambian sensiblemente:
La función ha mejorado casi un 20% su rendimiento, pero aún así queda bastante lejos de la solución con templates.
Y esto es solo la punta del iceberg, con templates podemos diseñar lo que se nos ocurra. Algunos ejemplos:
- Realizar conversiones de un tipo de dato cualquiera en otro usando únicamente una función: int valor = Convertir<int>("123456");
- Contenedores para almacenar colecciones de elementos: como ejemplo todos los de la STL
- ...
Eso sí, insisto. Dominar el tema de los templates no es sencillo y no hay demasiada gente que sea capaz de leerlos con soltura. Si alguien tiene ganas de coger un dolor de cabeza mientras explora la potencia de los templates puede echar un vistazo al libro: Modern C++ Design, de Andrei Alexandrescu.
Estáis avisados.
Un saludo.