Ver Mensaje Individual
  #7 (permalink)  
Antiguo 30/09/2015, 03:35
eferion
 
Fecha de Ingreso: octubre-2014
Ubicación: Madrid
Mensajes: 1.212
Antigüedad: 9 años, 6 meses
Puntos: 204
Respuesta: Escrudiñando el api de C++

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 original
  1. int factorial(int n)
  2. {
  3.   int toReturn=1;
  4.  
  5.   if(n>1)
  6.   {
  7.     do
  8.       toReturn *= n;
  9.     while(--n);
  10.   }
  11.  
  12.   return toReturn;
  13. }

Ahora la versión con templates:

Código C++:
Ver original
  1. template<int T>
  2. struct Factorial
  3. {
  4.   enum { valor=T*Factorial<T-1>::valor };
  5. };
  6.  
  7. template<>
  8. struct Factorial<0>
  9. {
  10.     enum { valor=1 };
  11. };

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 original
  1. const long MAX = 100000000;
  2. int main(int argc, char *argv[])
  3. {
  4.   QTime t;
  5.   t.start();
  6.   int acumulador = 0;
  7.   for( auto i=0L;i<MAX;i++)
  8.     acumulador += Factorial<10>::valor;
  9.  
  10.   std::cout << "Template: " << t.elapsed() << " ms"<< std::endl;
  11.  
  12.   t.start();
  13.   for( auto i=0L;i<MAX;i++)
  14.     acumulador += factorial(10);
  15.  
  16.   std::cout << "Funcion: " << t.elapsed() << " ms" << std::endl;
  17.  
  18.   // Esta línea la pongo para evitar que el compilador pueda hacer optimizaciones
  19.   // que eliminen el código de los bucles.
  20.   std::cout << acumulador << std::endl;
  21. }

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:

Código :
Ver original
  1. Template: 0 ms
  2. Funcion: 753 ms

¿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 original
  1. constexpr int factorial(int n)
  2. {
  3.   return (n>1)? n*factorial(n-1) : 1;
  4. }

Ejecutamos de nuevo el código y ahora los tiempos cambian sensiblemente:

Código :
Ver original
  1. Template: 0 ms
  2. Funcion: 595 ms

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.