En C++, el manejo de excepciones va acompañado de un mecanismo de liberación de pila (stack unwinding), que es la destrucción automática de objetos locales en la pila de llamadas creados antes de que ocurra una excepción. Este fenómeno es importante para la correcta liberación de recursos.
Originalmente, al diseñar C++, faltaba un recolector de basura incorporado, y la tarea de liberar recursos (memoria, archivos, sockets) recae en el programador. El manejo de excepciones debía asegurar la correcta liberación al retroceder en el código debido a un error.
Sin el mecanismo de destrucción automática de objetos al lanzarse una excepción, se producen fugas de recursos. Si no se liberan recursos manualmente en cada bloque catch, el código se vuelve complicado e inseguro.
C++ implementa la liberación de pila (stack unwinding), donde al lanzar una excepción todos los objetos ubicados en la pila que están en el ámbito antes del throw se destruyen en el orden inverso a su creación. Sus destructores se llaman automáticamente.
El enfoque clásico para liberar constantemente recursos es aplicar el patrón RAII (Resource Acquisition Is Initialization): todos los recursos "están envueltos" en objetos que liberan el recurso que poseen al destruirse.
Ejemplo de código:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("No se puede abrir el archivo"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // Se retrocederá si hay un throw // ... trabajar con el archivo throw std::runtime_error("Algún error"); // fclose se llamará automáticamente }
Características clave:
¿Por qué no se puede simplemente usar try-catch y llamar manualmente a delete o fclose en el bloque catch para evitar fugas?
Respuesta:
Es inconveniente e inseguro: es fácil olvidar cerrar un recurso, especialmente con múltiples puntos de salida o recursos anidados. Los destructores se llaman incluso si una excepción "pasa" a través de la función, no es necesario un catch para esto.
¿Se destruirán los objetos en el heap, si no están "envueltos" en objetos en la pila durante la liberación de pila?
Respuesta:
No. La liberación de pila solo llama a los destructores para los objetos ubicados en la pila. Para que los objetos del heap se destruyan correctamente, deben ser propiedad de un objeto en la pila (por ejemplo, a través de punteros inteligentes).
¿Qué ocurre si un destructor lanza una excepción durante la liberación de pila?
Respuesta:
Si durante la liberación de pila se lanza una segunda excepción al destruir cualquier objeto, el programa finalizará llamando a std::terminate(). ¡Nunca lance excepciones desde destructores!
El desarrollador cierra archivos y libera memoria manualmente a través de bloques catch sin aplicar RAII.
Ventajas:
Desventajas:
Archivos y recursos se envuelven en clases de envoltura RAII. Resultado: la liberación ocurre en los destructores, independientemente de throw/catch.
Ventajas:
Desventajas: