Introdução ao tratamento de erros em C++
O tratamento de erros é uma das tarefas principais no desenvolvimento de software confiável em C++. Diferente de algumas linguagens (como Python ou Java), onde as exceções são o único mecanismo padrão, C++ oferece ao desenvolvedor várias abordagens. A estabilidade, o desempenho e a legibilidade do código dependem da escolha da estratégia correta. Neste artigo, vamos analisar a construção clássica try/catch, os recursos modernos do padrão C++17/20, bem como métodos alternativos de tratamento de erros.
1. Abordagem clássica: construção try/catch
O mecanismo principal de tratamento de exceções em C++ é baseado em três palavras-chave: try, catch e throw. Uma exceção é um objeto de qualquer tipo que é "lançado" quando ocorre um erro e "capturado" no bloco correspondente.
#include <iostream>#include <stdexcept>
double divide(double a, double b) { if (b == 0) { throw std::runtime_error("Divisão por zero!"); } return a / b;}
int main() { try { double result = divide(10, 0); std::cout << "Resultado: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "Erro: " << e.what() << std::endl; } catch (...) { std::cerr << "Erro desconhecido!" << std::endl; } return 0;}Pontos importantes:
- Hierarquia de exceções: é recomendado lançar objetos de classes herdadas de
std::exception(por exemplo,std::runtime_error,std::logic_error). - Captura por referência: sempre capture exceções por referência constante (
const std::exception&), para evitar slicing e cópia desnecessária. - Manipulador universal:
catch (...)captura qualquer exceção, mas não dá acesso ao objeto de erro. Use-o apenas como último recurso.
2. Abordagens alternativas: códigos de retorno e std::optional
Nem sempre as exceções são a melhor escolha. Em sistemas de alta carga ou programação embarcada, frequentemente preferem-se mecanismos mais leves.
2.1. Códigos de retorno (error codes)
A função retorna um número inteiro ou uma enumeração, indicando sucesso ou o tipo de erro. Este método é simples e eficiente, mas requer verificação explícita do resultado.
enum class ErrorCode { Success, DivisionByZero, Overflow };
ErrorCode safe_divide(double a, double b, double& result) { if (b == 0.0) return ErrorCode::DivisionByZero; result = a / b; return ErrorCode::Success;}
int main() { double res; ErrorCode err = safe_divide(10, 0, res); if (err != ErrorCode::Success) { // Tratamento do erro }}2.2. std::optional (C++17)
Um contêiner que pode conter um valor ou estar vazio. Ideal para situações onde o erro é a ausência de resultado.
#include <optional>
std::optional<double> divide_opt(double a, double b) { if (b == 0.0) return std::nullopt; return a / b;}
int main() { auto result = divide_opt(10, 2); if (result) { std::cout << *result << std::endl; } else { std::cout << "Erro de divisão" << std::endl; }}3. Recursos modernos: noexcept e std::expected (C++23)
O padrão C++ continua evoluindo, oferecendo meios cada vez mais expressivos para trabalhar com erros.
3.1. Especificação noexcept
A palavra-chave noexcept indica que a função não lança exceções. Isso ajuda o compilador a otimizar o código e torna a interface mais clara.
void l