Sobes.tech
Middle+

Quando os destrutores não são chamados ao lançar uma exceção?

sobes.tech IA

Resposta da IA

Os destrutores de objetos locais com tempo de vida automático não são chamados se uma exceção for lançada no construtor e o objeto não tiver sido totalmente construído.

Além disso, os destrutores não são chamados para:

  • Objetos com tempo de vida global ou estático.
  • Objetos em memória bruta que não são geridos por mecanismos padrão (por exemplo, alocados via malloc e não libertados).
  • Objetos apontados por ponteiros inteligentes que estão a escapar.
  • Objetos cujos destrutores estão marcados como noexcept(false) e que lançam exceções durante a propagação da pilha (stack unwinding). Neste caso, o programa normalmente termina de forma anormal (std::terminate).

Exemplo de destrutor não chamado no construtor:

#include <iostream>
#include <stdexcept>

class Resource {
public:
    Resource() {
        std::cout << "Recurso adquirido" << std::endl;
    }
    ~Resource() {
        std::cout << "Recurso libertado" << std::endl;
    }
};

class MyClass {
public:
    Resource res1; // Este destrutor será chamado se uma exceção for lançada no construtor de MyClass
    // Resource res2; // Se aqui for lançada uma exceção, res2 não será construído

    MyClass() {
        // res1 já está construído
        std::cout << "Construtor de MyClass" << std::endl;
        throw std::runtime_error("Erro no construtor de MyClass");
        // res2 = Resource(); // Este objeto não será construído se uma exceção for lançada acima
    }
    ~MyClass() {
        std::cout << "Destrutor de MyClass" << std::endl;
    }
};

int main() {
    try {
        MyClass obj; // Aqui ocorre uma exceção
    } catch (const std::runtime_error& e) {
        std::cerr << "Exceção capturada: " << e.what() << std::endl;
    }
    return 0;
}

Neste exemplo, o destrutor de MyClass não é chamado porque o construtor de MyClass não foi concluído com sucesso. No entanto, o destrutor de res1 (parte de MyClass) será chamado, pois res1 foi construído com sucesso antes da exceção ser lançada.

Exemplo de destrutor não chamado durante uma exceção aninhada:

#include <iostream>
#include <stdexcept>

class BadDestructor {
public:
    BadDestructor() {
        std::cout << "Construtor de BadDestructor" << std::endl;
    }
    ~BadDestructor() noexcept(false) {
        std::cout << "Destrutor de BadDestructor" << std::endl;
        throw std::runtime_error("Erro no destrutor"); // Lança uma exceção
    }
};

void foo() {
    BadDestructor obj;
    throw std::runtime_error("Exceção em foo");
}

int main() {
    try {
        foo();
    } catch (const std::runtime_error& e) {
        std::cerr << "Exceção capturada: " << e.what() << std::endl;
    }
    // std::terminate será chamado antes de executar este bloco
    return 0;
}

Neste caso, quando a exceção é lançada em foo, a propagação da pilha começa. O destruidor de BadDestructor é chamado. Como este destruidor lança uma exceção (noexcept(false) permite isso), ocorre uma segunda exceção durante o tratamento da exceção atual. Segundo o padrão C++, nesta situação, std::terminate é chamado, e o programa termina. Os destrutores de outros objetos na pilha (se existissem antes de foo) podem não ser chamados.