Charlando sobre RAII

"Resource Acquisition Is Initialization" es uno de los idiomas característicos de C++. Quizás no muchos lo conozcan como tal y lo estén usando en de todas formas.
La idea es simple, aprovechar que la creación y la destrucción de objetos es determinística para manejar recursos, protegiéndose de la perdida o leakage de estos. En lenguajes basados en Maquinas Virtuales, la destrucción del objeto depende del momento en el que el Garbaje Collector lo destruye y no del momento en el que el objeto sale de scope. El idioma no se puede aplicar en estos lenguajes con tanta facilidad y se tiene que recurrir a otras estructuras de programación (En C# se ha introducido la palabra using al lenguaje para poder usar RAII, pero el código no queda tan limpio como en el caso de C++).

Veamos un ejemplo simple. Supongamos que tenemos que abrir un archivo, realizar alguna operación con el y después cerrarlo, o sea liberar el recurso asociado a el. Para esto disponemos de un API para trabajar con el file system basada en handlers.

Una primera aproximación para encarar el programa seria:

int open_file(const std::string & path);
...

void
close_file(int file_handle);

Supongamos ahora que según el contenido del archivo nuestra función realice distintas tareas:
void foo()
{

int
file_handle = open_file(" file.ext");
...

if
( no_procesar )
{

close_file(file_handle);
return
;
}
...

if
( caso_excepcional )
{

close_file(file_handle);
throw
( excepcion );
}
...

close_file(file_handle);
}

En este caso vemos como manejar correctamente el recurso, es decir, liberarlo cuando ya no sea necesario comienza a tornarse complicado.
La mantenibilidad del código es un dolor de cabeza.
El problema crece cuando se tiene en cuenta que en el medio de la función pueden aparecer excepciones. Tendremos entonces que tener en cuenta la liberación del recurso dentro de cada uno de los bloques catch. El código se volverá muy fragil. Java y C# tienen una estructura especial para evitar este problema que permite ejecutar un bloque de código siempre, aun cuando aparezca una excepción. C++ no posee tal estructura ya que mediante el uso de RAII se vuelve innecesaria.
El limite de fragilidad al que se puede llegar es muy alto:
void foo()
{

int
file_handle = open_file("file.ext");
...

if
( no_procesar )
{

close_file(file_handle);
return
;
}
...

if
( caso_excepcional )
{

close_file(file_handle);
throw
( excepcion );
}
...

try

{
...
}

catch
( excepcion_a e )
{

close_file(file_handle);
throw
(e);
}

catch
( excepcion_b e )
{

close_file(file_handle);
return
;
}
...

close_file(file_handle);
}

Aparece RAII. La idea entonces es hacer uso de la vida de los objetos.
Una de las formas de lograr mejorar el código es crear un wrapper que maneje el handle.
Por ejemplo:
class File
{

public
:
File(const std::string & path) :
file_handle( open_file(path) ) {};

~
File() { close_file(file_handle); }
...

private
:
int
file_handle;
};

El código anterior queda escrito como:
void foo()
{

File file("file.ext");
...

if
( no_procesar )
{

return
;
}
...

if
( caso_excepcional )
{

throw
( excepcion );
}
...

try

{
...
}

catch
( excepcion_b e )
{

return
;
}
}

El problema ha desaparecido. Cuando file salga de su scope, liberara el recurso que tiene asociado.
La robustez que se ha ganado es muy importante. Este método también tiene otras ventajas: podemos agregar al wrapper chequeos para asegurarnos que el archivo ha sido correctamente abierto por ejemplo. Es posible también ocultar completamente el API basado en handler agregando las funciones necesarias a esta clase, haciendo el código resultante independiente de este sistema de archivos en particular. Si en algun momento cambia el API, solo tendremos que modificar el wrapper. Todo esto puede ser logrado con overhead cero sobre el API ya que el compilador puede hacer inlining de las llamadas a cada una de las funciones.

De todas formas, hay ocasiones en donde no resulta cómodo realizar tal wrapper. Por ejemplo, cuando el API es demasiado grande y el esfuerzo en realizar los mismos es excesivo.
Podemos en este caso seguir utilizando RAII usando guardas.
Si tenemos el siguiente template:
template<class Handle, class Releaser>
class
handle_guard
{

public
:
handle_guard( Handle h, Releaser r ) :
handle(h), releaser(r) {}

~
handle_guard() { releaser(handle); }

private
:
Handle handle;
Releaser releaser;
};

Podríamos codificar la función anterior como:
class file_releaser
{

public
:
void
operator(int file_handle) const
{

close_file(file_handle);
}
};


void
foo()
{

handle_guard<int,file_releaser> guard(
open_file("file.ext"),
file_releaser() );
...

if
( no_procesar )
{

return
;
}
...

if
( caso_excepcional )
{

throw
( excepcion );
}
...

try

{
...
}

catch
( excepcion_b e )
{

return
;
}
}

Cuando la guarda salga de scope, utilizara el releaser para liberar el recurso que tiene asociado.

Los beneficios de este idioma son muy grandes y mejoran la robustez del código. Utilizar RAII es indispensable para la salud de un proyecto en C++.

C++ en español

No existen muchos recursos en español donde se discuta sobre lenguajes, y menos sobre C++. Parece ser que la idea le gusto a la gente de Blogger. El 24 de octubre apareció como "Blog of Note" del día y unas 6000 personas dieron una vuelta por la pagina.
En estos momentos estoy dedicándome a cerrar los últimos detalles de Boost.Bimap para poder presentarlo en una review formal en Boost. Va a estar bastante divertida la presentación, aviso cuando comience.
El 13 de octubre presente una charla en las "Sextas Jornadas de Software Libre" sobre mi experiencia en el GSoC 2006. El lunes 30 voy a repetir esta charla en La Plata, Argentina para estudiantes de Ingeniería Informática. Después de la charla voy a hablar sobre Boost y C++. Si alguien anda por ahí, se pueden acercar a la Facultad de Ingeniería Informática.
Seguiremos charlando sobre C++ entonces, ahora que parece haber gente interesada en aprender conmigo...