Sobes.tech
Middle+

Habla sobre los desafíos de lanzar excepciones en constructores y destructores.

sobes.tech AI

Respuesta de la IA

Llamar a una excepción en el constructor puede provocar fugas de recursos si parte de los objetos o recursos dentro del constructor se asignaron con éxito antes de que se lanzara la excepción, y el destructor correspondiente no se llame. Para prevenir fugas, se recomienda usar RAII (Adquisición de Recursos es Inicialización), por ejemplo, con punteros inteligentes o clases envolventes que garantizan liberar recursos al salir del ámbito.

Llamar a una excepción en el destructor conduce a un comportamiento indefinido. Si el destructor se llama como resultado de otra excepción (desenrollado de pila), y este mismo lanza una excepción no manejada, el programa terminará llamando a std::terminate. Según el estándar de C++, los destructores deben ser noexcept.

#include <iostream>
#include <memory> // Para std::unique_ptr

struct Resource {
    Resource() { std::cout << "Recurso adquirido\n"; }
    ~Resource() { std::cout << "Recurso liberado\n"; }
};

struct BadConstructor {
    Resource res; // Recurso asignado antes de lanzar la excepción
    BadConstructor() {
        std::cout << "Constructor BadConstructor\n";
        throw std::runtime_error("Error en el constructor"); // Lanzamiento de excepción
        // El destructor de Resource no será llamado si la excepción se lanza antes de completar el constructor
    }
    ~BadConstructor() noexcept { // Los destructores deben ser noexcept
        std::cout << "Destructor BadConstructor\n";
    }
};

struct GoodConstructor {
    std::unique_ptr<Resource> res; // Uso de puntero inteligente para RAII
    GoodConstructor() : res(std::make_unique<Resource>()) {
        std::cout << "Constructor GoodConstructor\n";
        // Ahora, si se lanza una excepción, unique_ptr garantiza llamar al destructor de Resource al salir del scope
        // throw std::runtime_error("Error en el constructor");
    }
    ~GoodConstructor() noexcept {
        std::cout << "Destructor GoodConstructor\n";
    }
};

struct BadDestructor {
    BadDestructor() { std::cout << "Constructor BadDestructor\n"; }
    ~BadDestructor() {
        std::cout << "Destructor BadDestructor\n";
        // No se debe lanzar excepción desde un destructor, especialmente durante el desenrollado
        // throw std::runtime_error("Error en el destructor");
    }
};

int main() {
    // Ejemplo con constructor (llevará a fuga de Resource sin RAII)
    try {
        // BadConstructor bc; // Descomentar para demostrar fuga
    } catch (const std::exception& e) {
        std::cout << "Excepción atrapada: " << e.what() << "\n";
    }

    // Ejemplo con buen constructor (usa RAII)
    try {
        // GoodConstructor gc; // Descomentar para demostrar, no habrá fuga
    } catch (const std::exception& e) {
        std::cout << "Excepción atrapada: " << e.what() << "\n";
    }

    // Ejemplo con destructor (puede llevar a std::terminate)
    try {
        // BadDestructor bd; // Descomentar si quieres ver un terminate potencial
        // throw std::runtime_error("Otra excepción"); // Excepción que causará unwind
    } catch (const std::exception& e) {
        std::cout << "Excepción atrapada: " << e.what() << "\n";
    }

    return 0;
}

Tabla resumen:

Lugar de llamada a excepción Problemas potenciales Solución recomendada
Constructor Fuga de recursos Usar RAII (punteros inteligentes, envoltorios)
Destructor Comportamiento indefinido, std::terminate durante unwind Los destructores deben ser noexcept, evitar lanzar excepciones