viernes, mayo 04, 2007

Trabajando en mi nuevo proyecto he tenido la oportunidad de retomar el trabajo con mi lenguaje de programación favorito (alguna chapuzilla tengo hecha pero en plan personal) y de poder aplicar mis escasos conocimientos en el desarrollo del mismo.

Aunque el párrafo anterior no era precisamente lo que tenía pensado contaros aunque me viene perfecto para introducir el "problema" que pretendía ilustrar:

Muchas veces cuando estamos desarrollando nuestras aplicaciones utilizamos asertos o similares para determinar errores. El problema de estas soluciones radica en que la mayoría de ellas son en tiempo de ejecución (dejar constancia que yo también lo hacía así :D). Un buen "chequeo estático" y mensajes de error configurables y explicativos son necesarios, sobre todo desde que la programación genérica se lleva cada vez más en el lenguaje C++. A continuación os propongo una pequeña utilidad para detectar errores en tiempo de compilación (con los consiguientes beneficios que ello conlleva).

Como bien os estareis imaginando la idea no es mía.(sinceramente ahora mismo no recuerdo si la idea original la leí en un artículo o en un libro de Alexandrescu). Vamos allá:

En primer lugar definamos una plantilla de clase del siguiente modo:

template struct Checker{
Checker(...);
};


A continuación hagamos una especialización parcial de la plantilla anterior:

template <> struct Checker { };

Vamos a aprovecharnos del preprocesador para construir mensajes de error configurables

#define CHECKING_ESTATICO(expresion, mensaje) \
{\
class ERROR_##mensaje{};\
(void)sizeof(Checker<(expresion)!= 0>((ERROR_##mensaje()))); \
}

¡OJO! No dejar espacios entre el nombre de la macro y los paréntesis

Vamos a darle utilidad a este chequeo en tiempo de compilación construyendo una versión "segura" de reinterpret_cast. Imaginaos que nuestro código realiza un reinterpret_cast de un tipo A a un tipo B y que estamos desarrollando nuestro proyecto en una máquina Z (nosotros sabemos que en nuestra máquina de desarrollo el tamaño del tipo A es menor o igual que el tamaño del tipo B). Ahora imaginaros que llevamos nuestro código a una máquina Y en la que no sabemos si el tamaño del tipo A es menor o igual que el tamaño del tipo B ....... ¿que ocurrirá?

Para ello desarrollemos, baśandonos en nuestra construcción anterior, una versión segura de reinterpret_cast que nos de un error en TIEMPO DE COMPILACIÓN en caso de que el tipo de destino del casting presente un tamaño menor que el origen. Actuaríamos del siguiente modo:

templateHacia, typename Desde>
Hacia reinterpret_cast_modo_seguro(Desde d){

CHECKING_ESTATICO(
sizeof(Desde) <= sizeof(Hacia),
Destino_Demasiado_Pequenio
);
return reinterpret_cast(d);
}

Podríamos utilizar esta función del mismo modo en que usamos reinterpret_cast.

Como podeis ver esto no es más que una ligera idea de como podemos usar los templates y el preprocesador para detectar errores en tiempo de compilación y enviar mensajes más o menos descriptivos. Podríamos construir, con poco trabajo, más funciones similares a la anterior que nos pueden resultar útiles (con la ayuda de la macro CHECKER).

Creo que esto es mucho más difícil explicarlo que entenderlo jejejje. Espero que os sirva de algo (posiblemente no pero así al menos he pasado la tarde que no me apetecía trabajar en mi fin de carrera).

Hasta pronto!
Un abrazo!

PD: creo que como mejor se entiende la macro CHECKER es mirando el código que genera el preprocesador. Suponiendo que tenemos el código fuente en el fichero tricks.cc, si queremos ver que código se genera una vez preprocesada la directiva #define no tenemos más que ejecutar la siguiente orden:

$> gcc -E -o tricks.i tricks.cc

La orden anterior crea el fichero tricks.i con el código generado por el preprocesador.

1 comentario:

cezonillo dijo...

Mono, no me cuentes estes milongues, que no me carajo de nada y luego me deprimo,jaja.

Seguro que cuando apruebe OPE me entero de todo,jojojo.

Cuentame algo de muyeres y pon fotos y déjate de C++ :P