Sobes.tech
Middle

É uma prática normal lançar exceções a partir de um construtor?

sobes.tech IA

Resposta da IA

Sim, esta é uma prática padrão e frequentemente necessária.

Se ocorrer um erro durante a construção do objeto que impeça que ele esteja em um estado correto e totalmente inicializado, lançar uma exceção é a única maneira correta de comunicar isso ao código chamador. O objeto do qual a exceção foi lançada a partir do construtor não é considerado completamente criado, e seu destruidor não será chamado automaticamente ao sair da pilha.

Isto é importante porque:

  1. Evita criar objetos incorretos, parcialmente inicializados.
  2. Sinaliza um erro de inicialização que o código chamador pode tratar.
  3. Está de acordo com o princípio RAII (Resource Acquisition Is Initialization), onde a conclusão bem-sucedida do construtor garante o estado correto do objeto e a gestão de recursos.

Também é preciso ter cuidado com os recursos que foram alocados ou inicializados antes de lançar a exceção. Eles devem ser liberados ou revertidos dentro do bloco try-catch do próprio construtor ou usando wrappers RAII (por exemplo, ponteiros inteligentes) que limpam automaticamente os recursos ao sair do escopo (mesmo em caso de exceção).

Exemplo:

#include <stdexcept>
#include <new>
#include <iostream>

class MyClass {
public:
    // Construtor que pode lançar exceção
    MyClass(size_t size) : data_(nullptr) {
        if (size == 0) {
            // Lança exceção se o tamanho for inválido
            throw std::invalid_argument("Size cannot be zero");
        }
        try {
            // Tenta alocar memória. new pode lançar std::bad_alloc
            data_ = new int[size];
            std::cout << "Memory allocated." << std::endl;
        } catch (const std::bad_alloc& e) {
            // Re-lança a exceção após tratamento
            std::cerr << "Memory allocation failed: " << e.what() << std::endl;
            throw; // Re-lança a exceção capturada
        }
        // Se tudo ocorreu bem, o objeto foi criado com sucesso
        std::cout << "Object constructed successfully." << std::endl;
    }

    // O destruidor é chamado apenas para objetos completamente criados
    ~MyClass() {
        std::cout << "Destructor called." << std::endl;
        delete[] data_;
    }

private:
    int* data_;
};

int main() {
    try {
        std::cout << "Attempting to create object with size 10..." << std::endl;
        MyClass obj1(10); // Cria com sucesso
        // O destruidor de obj1 será chamado ao sair do try
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    std::cout << std::endl;

    try {
        std::cout << "Attempting to create object with size 0..." << std::endl;
        MyClass obj2(0); // Lançará exceção
        // O destruidor de obj2 NÃO será chamado, pois o construtor não terminou
    } catch (const std::exception& e) {
        std::cerr << "Caught exception: " << e.what() << std::endl;
    }

    return 0;
}