Tratamento de Erros em C++: de try/catch a RAII e noexcept
A confiabilidade de um programa depende diretamente de como ele reage a situações imprevistas. Em C++, o mecanismo principal para isso são as exceções. Neste artigo, vamos analisar a construção clássica try/catch/throw, bem como abordagens alternativas e modernas: RAII, noexcept e tratamento de erros sem exceções.
1. Mecanismo Básico: try, catch, throw
Uma exceção é um objeto que é "lançado" (throw) quando ocorre um erro. O programa pode capturá-lo (catch) e executar ações corretivas sem interromper completamente o funcionamento.
Sintaxe
#include <iostream>#include <stdexcept>
double divide(double a, double b) { if (b == 0.0) { throw std::runtime_error("Divisão por zero!"); } return a / b;}
int main() { try { double result = divide(10.0, 0.0); std::cout << "Resultado: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "Erro: " << e.what() << std::endl; } return 0;}Regras de Funcionamento
- throw — cria uma cópia do objeto e transfere o controle para o manipulador.
- try — bloco de código onde podem ocorrer exceções.
- catch — captura a exceção por tipo. Vários blocos
catchpodem ser especificados. - Se a exceção não for capturada,
std::terminate()é chamado.
Captura pela Classe Base
Todas as exceções padrão herdam de std::exception. Portanto, podemos escrever um manipulador universal:
try { // código} catch (const std::exception& e) { std::cerr << "Erro padrão: " << e.what() << std::endl;} catch (...) { std::cerr << "Exceção desconhecida" << std::endl;}O bloco catch (...) captura todas as exceções, mas sem acesso ao objeto.
2. Exceções em Construtores e Destrutores
Construtores
Se um construtor lançar uma exceção, o objeto é considerado não criado e o destrutor para ele não é chamado. Isso é importante para o gerenciamento de recursos. Exemplo:
class Resource {public: Resource() { std::cout << "Recurso alocado" << std::endl; throw std::runtime_error("Erro de inicialização"); } ~Resource() { std::cout << "Recurso liberado" << std::endl; }};
int main() { try { Resource r; } catch (...) { std::cout << "Exceção capturada" << std::endl; } // O destrutor de Resource NÃO será chamado! return 0;}Destrutores
Lançar exceções de um destrutor é uma má prática. Se o destrutor for chamado durante o desenrolamento da pilha (stack unwinding) e lançar uma nova exceção, o programa será encerrado abruptamente. Sempre escreva destrutores como noexcept (por padrão, eles já são).
3. RAII — Gerenciamento Automático de Recursos
O idioma RAII (Resource Acquisition Is Initialization) é a chave para um código seguro. O recurso (memória, arquivo, mutex) é adquirido no construtor e liberado no destrutor. Se ocorrer uma exceção, os destrutores de todos os objetos criados são chamados automaticamente (stack unwinding) e os recursos são liberados.
Exemplo com o smart pointer std::unique_ptr:
#include <memory>#include <iostream>
void process() { auto ptr = std::make_unique<int>(42); // Se uma exceção ocorrer aqui, // unique_ptr liberará a memória automaticamente throw std::runtime_error("Erro em proc");