C++: Optimizaciones del compilador

Hoy, durante la primera reunión de la Cátedra que hemos tenido en SAES, se me ha ocurrido una idea para hacer logging de una aplicación en C++. Hace un tiempo leí en el blog de mi amigo Boris Kolpackov cómo el compilador de C++ es lo suficientemente inteligente como para darse cuenta de que cierto código no lo debe generar.

El caso del log es tal que a veces se quiere activar y otras veces desactivar de una aplicación. Cuando se desactiva para la aplicación que se entregará al usuario, cierto código de logging no tendría que aparecer (ni que ocupar tiempo) de la aplicación entregada. Sin embargo, durante el desarrollo, es interesante que el log esté activo.

Así pues, ¿cómo conseguir que el mismo código aparezca o no? La opción más común es utilizar el preprocesador para definir algún macro de depuración. Estos macros al final son algo extraños, porque utilizan el preprocesador y no objetos normales de C++, lo cual queda algo extraño y poco elegante. Viendo su artículo, se me ocurrió que se podría especializar un patrón (template) en el que la versión por defecto no hiciera nada (no hay log), y la versión activada enviaba la salida al log. He aquí una posible implementación:


#include <iostream>

class logger_base
{
public:
logger_base (std::ostream& s)
: s_ (s)
{
}

protected:
std::ostream& s_;
};

template <bool b>
class logger: public logger_base
{
public:
logger (std::ostream& s)
: logger_base (s)
{
}

template <typename T>
friend logger<b>&
operator<< (logger<b> const& l, T const& t)
{
// Default: do nothing
return const_cast<logger<b>&>(l);
}

template <typename T>
friend logger<b>&
operator<< (logger<b> const& l, char const* t)
{
// Default: do nothing
return const_cast<logger<b>&>(l);
}
};


template<>
class logger<true> : public logger_base
{
public:
logger (std::ostream& s)
: logger_base (s)
{
}

template <typename T>
friend logger<true>&
operator<< (logger<true> const& l, T const& t)
{
// Do now: log
logger<true>& _l = const_cast<logger<true>&>(l);
_l.s_ << t;
return _l;
}

template <typename T>
friend logger<true>&
operator<< (logger<true> const& l, char*const t)
{
// Do now: log
logger<true>& _l = const_cast<logger<true>&>(l);
_l.s_ << t;
return _l;
}
};

// DO LOG
typedef logger<true> logger_t;

int
main (int argc, char**argv)
{
logger_t log (std::cout);

log << "abc" << "cde";

return 0;

}


El código, primero, define un patrón que se especializa parcialmente a posteriori. Como se ha dicho, la implementación por defecto es no hacer nada. La clase logger<T> implementa el operator<< de tal forma que no hace absolutamete nada. La clase acepta cualquier std::ostream, con lo que se puede mostrar por pantalla o por cualquier fichero, socket, etc.

El código del patrón se especialza para el caso "true". Si el patrón se crea con este valor, ahora los operadores realizan la función de log de forma normal.

El usuario puede, entonces, definir el tipo logger_t como un typedef del logger adecuado, y utilizar a partir de ese momento logger_t durante todo el código.

Nótese cómo ahora no hay ningún elemento extraño ni macro más o menos afortunado. El logging se hace con streams de C++.

Ahora bien. La preunta que surge es: cuando se selecciona el patrón "false" se genera código o se elimina? Primero veamos qué genera el compilador para el caso "true" (con optimización -O2):


main:
.LFB1037:
subq $8, %rsp
.LCFI1:
movl $3, %edx
movl $.LC0, %esi
movl $_ZSt4cout, %edi
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
movl $3, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
xorl %eax, %eax
addq $8, %rsp
ret


Véase cómo el compilador reduce las llamadas inline a una llamada a ostream::insert dos veces (con las dos cadenas), y el log se produce. Ahora para el caso "false" (también con -O2):


main:
.LFB1037:
xorl %eax, %eax
ret


Esto es, el compilador no produce ningún código (el valor 0 se retorna en eax), y optimiza automáticamente todas las llamadas al log. Estos mecanismos de metaprogramación hacen a C++ todavía más potente de lo que parece a simple vista.

blog comments powered by Disqus