Historia de la cuestión:
C++ fue diseñado originalmente con un enfoque en el rendimiento, por lo que la gestión de recursos (memoria, archivos, flujos, sockets) se realiza con mayor frecuencia de forma manual. Cuando ocurre una excepción, es necesario limpiar los recursos capturados. El desenrollado de pila (stack unwinding) es el mecanismo utilizado por C++ para finalizar correctamente las funciones durante el lanzamiento de una excepción.
Problema:
Al lanzar una excepción, el control se transfiere inmediatamente a los bloques catch, y las funciones intermedias se "desenrollan": sus destructores son llamados, pero las llamadas explícitas a las funciones de liberación pueden ser omitidas (por ejemplo, si no se utilizan objetos que liberan recursos automáticamente).
Solución:
En C++, la liberación de recursos debe ser delegada a los destructores, en lugar de llamar a las funciones de liberación manualmente. El patrón RAII (Resource Acquisition Is Initialization) es una manera clara de hacer que la liberación del recurso sea automática. Durante el desenrollado de pila, se llamará al destructor, y el recurso se liberará independientemente del camino de salida de la función.
Ejemplo de código:
#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // Se abrirá y cerrará correctamente incluso si hay una excepción if (!file.is_open()) { throw std::runtime_error("El archivo no se puede abrir"); } // ... leer el archivo } // file se cerrará incluso si hay una excepción
Características clave:
¿Es suficiente tener delete ptr; en el bloque catch para limpiar la memoria?
No, si entre la asignación de memoria y el bloque catch ocurre una excepción, la memoria podría no ser liberada. Es mejor usar std::unique_ptr o escribir delete en el destructor.
Ejemplo de código:
void foo() { int* data = new int[10]; // ... throw std::runtime_error("fallo"); delete[] data; // no se llamará en caso de excepción }
¿Puede el desenrollado de pila omitir la llamada al destructor de un objeto que se encuentra en la pila?
No, todos los objetos locales (no destruidos hasta el punto de lanzamiento de la excepción) serán destruidos en el orden inverso a su creación, los destructores serán llamados de forma garantizada.
¿Se puede usar goto o longjmp para salir del bloque try y contar con el llamado a los destructores?
No. C++ garantiza la llamada a destructores solo durante el desenrollado de pila debido a una excepción, no debido a un control de flujo incorrecto (goto, setjmp/longjmp).
Un programador asigna memoria usando new, maneja excepciones liberando memoria en el bloque catch, pero olvida otros caminos de salida de la función.
Pros:
Contras:
Se utiliza std::unique_ptr y clases RAII para todos los recursos, la liberación no depende de try/catch.
Pros:
Contras: