Sobes.tech
Middle+

Fale sobre os desafios de lançar exceções (throw exception) nos construtores e destrutores.

sobes.tech IA

Resposta da IA

Chamar uma exceção no construtor pode levar a fugas de recursos se parte dos objetos ou recursos dentro do construtor foram atribuídos com sucesso antes do lançamento da exceção, e o destruidor correspondente não for chamado. Para evitar fugas, recomenda-se usar RAII (Aquisição de Recursos é Inicialização), por exemplo, com ponteiros inteligentes ou classes envolventes que garantem liberar recursos ao sair do escopo.

Chamar uma exceção no destruidor leva a um comportamento indefinido. Se o destruidor for chamado como resultado de outra exceção (desenrolar da pilha), e este lançar uma exceção não tratada, o programa terminará chamando std::terminate. De acordo com o padrão C++, os destrutores devem 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 será alocado antes do lançamento da exceção
    BadConstructor() {
        std::cout << "Construtor BadConstructor\n";
        throw std::runtime_error("Erro no construtor"); // Lançamento de exceção
        // O destruidor de Resource não será chamado se a exceção for lançada antes do final do construtor
    }
    ~BadConstructor() noexcept { // Os destrutores devem ser noexcept
        std::cout << "Destruidor BadConstructor\n";
    }
};

struct GoodConstructor {
    std::unique_ptr<Resource> res; // Uso de ponteiro inteligente para RAII
    GoodConstructor() : res(std::make_unique<Resource>()) {
        std::cout << "Construtor GoodConstructor\n";
        // Agora, se lançar uma exceção, o unique_ptr garante a chamada ao destruidor de Resource ao sair do escopo
        // throw std::runtime_error("Erro no construtor");
    }
    ~GoodConstructor() noexcept {
        std::cout << "Destruidor GoodConstructor\n";
    }
};

struct BadDestructor {
    BadDestructor() { std::cout << "Construtor BadDestructor\n"; }
    ~BadDestructor() {
        std::cout << "Destruidor BadDestructor\n";
        // Não se deve lançar exceções de um destruidor, especialmente durante o unwind
        // throw std::runtime_error("Erro no destruidor");
    }
};

int main() {
    // Exemplo com construtor (levará a fuga de Resource sem RAII)
    try {
        // BadConstructor bc; // Descomentar para demonstração de fuga
    } catch (const std::exception& e) {
        std::cout << "Exceção capturada: " << e.what() << "\n";
    }

    // Exemplo com bom construtor (usa RAII)
    try {
        // GoodConstructor gc; // Descomentar para demonstração, não haverá fuga
    } catch (const std::exception& e) {
        std::cout << "Exceção capturada: " << e.what() << "\n";
    }

    // Exemplo com destruidor (pode levar a std::terminate)
    try {
        // BadDestructor bd; // Descomentar se desejar ver um terminate potencial
        // throw std::runtime_error("Outra exceção"); // Exceção que causará unwind
    } catch (const std::exception& e) {
        std::cout << "Exceção capturada: " << e.what() << "\n";
    }

    return 0;
}

Tabela resumida:

Local de chamada da exceção Problemas potenciais Solução recomendada
Construtor Fuga de recursos Usar RAII (ponteiros inteligentes, wrappers)
Destruidor Comportamento indefinido, std::terminate durante unwind Os destrutores devem ser noexcept, evitar lançar exceções