É 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:
- Evita criar objetos incorretos, parcialmente inicializados.
- Sinaliza um erro de inicialização que o código chamador pode tratar.
- 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;
}