Sobes.tech
Middle+

É possível lançar uma exceção a partir de um destruidor e capturá-la?

sobes.tech IA

Resposta da IA

Pode, mas é altamente desaconselhável. Lançar uma exceção a partir de um destruidor pode levar a um comportamento indefinido, especialmente se o destruidor for chamado durante a propagação de outra exceção.

#include <iostream>
#include <stdexcept>

struct Resource {
    ~Resource() {
        // É altamente desaconselhável lançar exceções a partir de um destruidor
        std::cerr << "Destruidor Resource chamado." << std::endl;
        // throw std::runtime_error("Exceção do destruidor Resource");
    }
};

void func_with_exception() {
    Resource r;
    throw std::runtime_error("Exceção em func_with_exception");
}

int main() {
    try {
        func_with_exception();
    } catch (const std::exception& e) {
        // Se uma exceção for lançada a partir de um destruidor durante a propagação da pilha
        // aqui, pode ocorrer terminate() ou outro comportamento indefinido.
        std::cerr << "Exceção capturada: " << e.what() << std::endl;
    }
    return 0;
}

Razões principais pelas quais não se deve lançar exceções a partir de destrutores:

  • Exceções duplas: Se o destruidor for chamado durante o tratamento de outra exceção (por exemplo, durante a propagação da pilha), lançar uma nova exceção resultará na coexistência de duas exceções ativas, o que é um comportamento indefinido e pode levar a std::terminate().
  • Limpeza incompleta de recursos: Se ocorrer um erro no destruidor e uma exceção for lançada, as operações de limpeza subsequentes no mesmo destruidor podem não ser executadas, levando a vazamentos de recursos ou outros problemas.
  • Gestão complexa de erros: Lidar com exceções em destrutores complica a lógica do programa e dificulta a compreensão do fluxo de execução.

Em C++11 e versões posteriores, os destrutores são considerados noexcept por padrão, o que significa que não devem lançar exceções. Se um destruidor noexcept lançar uma exceção, std::terminate() será chamado imediatamente. Para permitir que um destruidor lance exceções, deve-se declará-lo explicitamente como noexcept(false).

#include <iostream>
#include <stdexcept>

struct ResourceMayThrow {
    ~ResourceMayThrow() noexcept(false) {
        std::cerr << "Destruidor ResourceMayThrow chamado." << std::endl;
        // Neste caso, uma exceção pode ser lançada, mas ainda assim não é recomendado
        // throw std::runtime_error("Exceção do destruidor ResourceMayThrow");
    }
};

int main() {
    try {
        ResourceMayThrow r;
        // Se aqui ocorrer uma exceção, e o destruidor também lançar uma exceção,
        // isso levará a std::terminate().
    } catch (const std::exception& e) {
        std::cerr << "Exceção capturada em main: " << e.what() << std::endl;
    }
    return 0;
}

Em vez de lançar exceções a partir de destrutores, recomenda-se usar as seguintes abordagens para lidar com erros ao liberar recursos:

  • Retornar código de erro: Se o destruidor puder falhar, a estrutura pode manter uma flag de erro que pode ser verificada após a sua execução.
  • Registro de erros: Registrar erros ao liberar recursos em um arquivo de log ou no fluxo de erro padrão.
  • Implementar funções close() ou release(): Fornecer uma função separada para fechar explicitamente o recurso, que pode lançar exceções, e chamá-la antes de sair do escopo do objeto. Objetos RAII devem garantir a liberação de recursos em seus destrutores, mesmo que close() não seja chamado explicitamente.
Abordagem Recomendado? Notas
Lançar exceção Não Pode levar a std::terminate() e comportamento indefinido.
Retornar código de erro Sim (se necessário) Requer verificar explicitamente o erro após usar o objeto.
Registro de erros Sim Útil para depuração, mas não interrompe a execução do programa.
Função close/release Sim Permite lidar explicitamente com erros ao liberar, o destruidor é seguro.

De modo geral, um destruidor seguro é aquele que nunca lança exceções, o que faz parte do princípio RAII (Resource Acquisition Is Initialization).