Introdução: Por que o tratamento de erros é a base de um código confiável em C++
Qualquer programa que interage com o mundo externo (arquivos, rede, entrada do usuário) inevitavelmente encontra situações excepcionais. Em C++, o mecanismo try/catch é a principal ferramenta para gerenciar essas situações de forma elegante e controlada. Neste artigo, abordaremos o tratamento de erros em C++, desde construções básicas até técnicas avançadas, incluindo best practices e armadilhas comuns.
1. Fundamentos: try, catch e throw
O mecanismo de exceções em C++ é construído sobre três palavras-chave: try, catch e throw.
1.1. Bloco try e catch
O bloco try contém o código que pode lançar uma exceção. O bloco catch captura essa exceção e a trata.
#include <iostream>#include <stdexcept>
int main() { try { // Código que pode causar um erro int age = -5; if (age < 0) { throw std::invalid_argument("A idade não pode ser negativa"); } std::cout << "Idade: " << age << std::endl; } catch (const std::invalid_argument& e) { std::cerr << "Erro: " << e.what() << std::endl; } return 0;}1.2. Tipos de exceções
Em C++, você pode lançar exceções de qualquer tipo: números, strings, objetos de classe. No entanto, é recomendado usar as classes de exceção padrão de <stdexcept> ou criar suas próprias classes herdadas de std::exception.
- Exceções padrão:
std::runtime_error,std::logic_error,std::out_of_range, etc. - Exceções personalizadas: criadas através de herança de
std::exception.
class MyException : public std::exception {public: const char* what() const noexcept override { return "Minha própria exceção"; }};1.3. Múltiplos blocos catch
Você pode capturar diferentes tipos de exceções em blocos catch separados. A ordem é importante: exceções mais específicas devem vir primeiro.
try { // código} catch (const std::invalid_argument& e) { // tratamento de invalid_argument} catch (const std::exception& e) { // tratamento de todas as outras exceções de std} catch (...) { // catch-all: captura tudo que não foi capturado acima}2. Técnicas Avançadas: Pilha de Chamadas e RAII
2.1. Desenrolamento da Pilha (Stack Unwinding)
Quando uma exceção é lançada, o C++ automaticamente destrói todos os objetos locais no caminho do ponto de throw até o catch correspondente. Esse processo é chamado de desenrolamento da pilha. Ele garante que os destrutores de todos os objetos criados nos blocos try sejam chamados.
class Resource {public: Resource() { std::cout << "Recurso criado"; } ~Resource() { std::cout << "Recurso destruído"; }};
void riskyFunction() { Resource res; throw std::runtime_error("Algo deu errado");}
int main() { try { riskyFunction(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } // Saída: Recurso criado -> Recurso destruído -> Algo deu errado}2.2. RAII (Resource Acquisition Is Initialization)
RAII é um idioma fundamental do C++ que está intimamente ligado ao tratamento de erros. A ideia: o gerenciamento de recursos (memória, arquivos, mutexes) é vinculado ao tempo de vida do objeto. O destrutor libera automaticamente o recurso, mesmo em caso de exceção.
- Ponteiros inteligentes:
std::unique_ptr,std::shared_ptr