Artículos de la serie:
Técnica RAII (segunda parte)
La técnica RAII no se circunscribe únicamente a la asignación dinámica de memoria, sino que afecta a cualquier tipo de recurso, sea éste un fichero, un mutex (programación concurrente), un socket (networking), etcétera.
Este tratamiento homogéneo de recursos en C++ contrasta con el de otros lenguajes de programación orientados a objetos, en los que debe distinguirse claramente entre los mecanismos de recolección de basura (ligados a la gestión de la memoria dinámica) y los bloques try-catch-finally (a utilizar con otros tipos de recursos). Cabe decir que, frente al esquema tradicional de adquisición y liberación de recursos en bloques try-catch-finally, Java 7 y superiores ofrecen la construcción alternativa try-with-resources [1]. Análogamente, C# 8.0 y superiores proporcionan los denominados using statements [2]. Por su parte, Python 2.5 y superiores poseen los with statements [3]. En todos los casos, sin embargo, se trata de soluciones parciales que no poseen el alcance y generalidad de la técnica RAII.
Como ejemplo clásico de empleo de la técnica RAII en recursos distintos a la memoria, consideremos el siguiente código inseguro ante excepciones, que no garantiza el cierre del fichero al término de la función:
void file_test(char const* name, char const* mode)
{
std::FILE* const file = std::fopen(name, mode);
if (!file)
throw std::runtime_error{"Unable to open the file"};
// usamos el fichero (este código pudiese emitir excepciones)...
std::fclose(file); // cerramos el fichero
}
En efecto, de lanzarse una excepción en algún punto de ejecución intermedio entre la apertura del fichero y su cierre al témino de la función, la sentencia std::fclose(file) nunca llegaría a ejecutarse, produciéndose la fuga del recurso. Nuevamente, podemos hacer frente a tal eventualidad mediante la introducción de un bloque de limpieza try-catch de la forma:
void file_test(char const* name, char const* mode)
{
std::FILE* const file = std::fopen(name,mode);
if (!file)
throw std::runtime_error{"Unable to open the file"};
try {
// usamos el fichero (posible emisión de excepciones)...
}
catch(...) { // capturamos cualquier excepción,
std::fclose(file); // cerramos el fichero
throw; // y relanzamos la excepción
}
std::fclose(file); // en caso de que no se hayan emitido excepciones
}
Haciendo uso de la técnica RAII, sin embargo, podemos optar por definir una clase File_handle cuyo constructor abra el archivo y cuyo destructor se encargue de cerrarlo y permitir que la vida del recurso quede ligada a la duración de almacenamiento de un objeto local de esta clase [4]:
class File_handle {
std::FILE* file_;
public:
// constructor y destructor:
File_handle(char const* name, char const* mode)
: file_{std::fopen(name,mode)}
{
if (!file_)
throw std::runtime_error{"Unable to open the file"};
}
~File_handle()
{
if (file_) // protección ante posible fallo de segmentación
std::fclose(file_);
}
// resto de la interfaz pública (operaciones input/output, etc)
};
Pudiendo prescindir del bloque try-catch, la función file_test puede ahora reescribirse de forma más limpia y eficiente como:
void file_test(char const* name, char const* mode)
{
auto file = File_handle {name, mode};
// usamos el fichero (el código puede emitir excepciones)...
} // File_handle::~File_handle() cierra el fichero automáticamente
Observemos cómo el destructor de la clase File_handle se encarga de cerrar el archivo tanto si se emite una excepción durante la ejecución de la función file_test() como si ésta finaliza normalmente.
En conclusión, en C++ se emplea la técnica RAII para establecer un orden determinista en la gestión de recursos. En particular, se desaconseja el uso explícito de expresiones new y delete en código, salvo internamente en clases que implementen la técnica RAII. Para ello, la biblioteca estándar del lenguaje C++ incluye, entre otras muchas funcionalidades:
- Punteros inteligentes (std::unique_ptr<>, std::shared_ptr<> y std::weak_ptr<>).
- Estructuras dinámicas de datos (std::vector<>, std::queue<>, std::stack<>, std::list<>, std::map<>, std::unordered_map<>, std::set<>, std::unordered_set<>, etc.).
- std::fstream y similares para la gestión de ficheros.
- std::string y similares para la representación de cadenas de caracteres.
- Y un largo etcétera.
Referencias bibliográficas:
- Java try-with-resources – https://en.wikipedia.org/wiki/Java_syntax#try-with-resources_statements
- C# using statements – https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement
- Python - The 'with' statement - https://www.python.org/dev/peps/pep-0343/
- http://www.stroustrup.com/bs_faq2.html#exceptions
No hay comentarios:
Publicar un comentario