Tratamento de Erros em C++: do try/catch às Abordagens Modernas
\n\nO mecanismo de exceções em C++ é uma ferramenta poderosa, mas que exige profundo entendimento. Enquanto o uso básico de try/catch é conhecido pela maioria dos desenvolvedores, o tratamento profissional de erros envolve muito mais: desde garantias de segurança de exceções até estratégias alternativas sem exceções. Neste artigo, analisaremos técnicas avançadas para lidar com erros no C++ moderno.
1. Garantias de Segurança de Exceções: Fundamento do Código Confiável
\n\nQualquer função que possa lançar uma exceção deve fornecer uma das três garantias de segurança:
\n\n- \n
- Garantia Básica (Basic Guarantee): em caso de exceção, os invariantes do objeto são preservados e não há vazamentos de recursos. O objeto pode estar em um estado imprevisível, mas válido. \n
- Garantia Forte (Strong Guarantee): a operação é concluída com sucesso ou o estado do programa permanece inalterado (atomicidade). É alcançada através do idiom copy-and-swap. \n
- Garantia de Ausência de Exceções (Noexcept Guarantee): a função nunca lança exceções. É crucial para destrutores, operações de move e operações usadas em contêineres. \n
// Exemplo de garantia forte via copy-and-swap\nclass Vector {\n int* data;\n size_t sz, cap;\npublic:\n void push_back(int value) {\n if (sz == cap) {\n // 1. Criamos uma cópia\n Vector tmp(*this);\n // 2. Modificamos a cópia (pode lançar exceção)\n tmp.reserve(cap * 2);\n // 3. Troca (noexcept!)\n swap(tmp);\n }\n data[sz++] = value; // noexcept\n }\n \n void swap(Vector& other) noexcept {\n std::swap(data, other.data);\n std::swap(sz, other.sz);\n std::swap(cap, other.cap);\n }\n};\n\n2. RAII como Base do Gerenciamento de Recursos
\n\nO idiom RAII (Resource Acquisition Is Initialization) não se resume a ponteiros inteligentes. É um princípio fundamental que permite evitar vazamentos de recursos durante exceções:
\n\n- \n
- O recurso é adquirido no construtor \n
- É liberado no destrutor (que por padrão é
noexcept) \n - Uma exceção no destrutor é UB (comportamento indefinido) \n
// Wrapper RAII para descritor de arquivo\nclass FileDescriptor {\n int fd;\npublic:\n explicit FileDescriptor(const char* path) \n : fd(open(path, O_RDONLY)) {\n if (fd == -1) throw std::runtime_error("Falha ao abrir arquivo");\n }\n \n ~FileDescriptor() noexcept {\n if (fd != -1) close(fd);\n }\n \n // Proibimos cópia, permitimos move\n FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {\n other.fd = -1;\n }\n};\n\n3. Alternativas Modernas às Exceções: std::optional e std::expected
\n\nNem todos os erros devem ser tratados com exceções. Para erros esperados (entrada inválida, ausência de dados), é melhor usar tipos wrapper:
\n\n#include <optional>\n#include <expected> // C++23\n\n// std::optional — para valores opcionais\nstd::optional<int> safe_divide(int a, int b) {\n if (b == 0) return std::nullopt;\n return a / b;\n}\n\n// std::expected — para erros com informação (C++23)\nstd::expected<double, std::string> sqrt_checked(double x) {\n if (x < 0) return std::unexpected("Entrada negativa");\n return std::sqrt(x);\n}\n\n// Uso\nauto result = safe_divide(10, 0);\nif (result) {\n std::cout << *result;\n} else {\n std::cout << "Divisão por zero";\n}\n\n4. noexcept: Especificação e Otimizações
\n\nO especificador noexcept não é apenas documentação. Ele afeta o desempenho e