Da trilha de C++ Este conceito ainda não saiu como card do dia. ir para o card de hoje

C++ Erros Intermediário

Exceções

Em C++, exceções são o mecanismo padrão para separar a detecção de um erro do seu tratamento. Quando algo dá errado, usamos throw para lançar um objeto; quem chamou pode capturá-lo com try/catch. Isso evita códigos de erro espalhados e permite que a pilha de chamadas seja desenrolada automaticamente, destruindo objetos locais (RAII).

A biblioteca padrão define a hierarquia std::exception com classes como std::runtime_error, std::logic_error e std::out_of_range. Capturar por referência (const std::exception&) evita slicing e permite acessar what(). Prefira lançar tipos que herdam de std::exception para interoperar com código genérico.

A palavra-chave noexcept promete que uma função não lança exceções. Se ela lançar, o programa chama std::terminate. Isso é essencial para destrutores, move constructors e operações usadas em containers, pois permite otimizações e garante segurança em situações de emergência.

Por fim, as garantias de exceção descrevem o que uma operação assegura se uma exceção for lançada: básica (nenhum recurso vaza, estado válido), forte (rollback atômico) e nothrow (nunca lança). Entender essas garantias ajuda a projetar código robusto e a documentar contratos.

Pontos-chave

  • throw lança um objeto; try/catch captura por tipo, preferencialmente por referência (const std::exception&).
  • A hierarquia std::exception padroniza erros; use std::runtime_error para falhas de execução e std::logic_error para erros de programação.
  • noexcept indica que uma função não lança; se lançar, std::terminate é chamado.
  • As garantias de exceção (básica, forte, nothrow) definem o estado do programa após uma falha.
  • Destrutores e move constructors devem ser noexcept para segurança e desempenho.
excecoes_basico.cpp
#include <iostream>
#include <stdexcept>
#include <string>

// Função que pode lançar exceção
double divide(double a, double b) {
    if (b == 0.0)
        throw std::runtime_error("divisao por zero");
    return a / b;
}

int main() {
    try {
        std::cout << "10 / 2 = " << divide(10, 2) << '\n';
        std::cout << "10 / 0 = " << divide(10, 0) << '\n'; // lança
        std::cout << "nunca chega aqui\n";
    }
    catch (const std::exception& e) {
        std::cerr << "Erro capturado: " << e.what() << '\n';
    }
    std::cout << "Fim do programa.\n";
    return 0;
}

Exercícios

  1. 1
    Fácil

    Divisão segura com exceção

    Escreva uma função safe_divide que recebe dois double e retorna a / b. Se b for zero, lance std::invalid_argument com a mensagem "divisao por zero". No main, chame a função com (10, 2) e (5, 0), capturando a exceção e imprimindo a mensagem.

    Resolver
  2. 2
    Médio

    Hierarquia de exceções personalizada

    Crie uma hierarquia de exceções para uma conta bancária: SaldoInsuficiente herda de std::runtime_error e ValorInvalido herda de std::logic_error. Implemente sacar(double valor) que lança ValorInvalido se valor <= 0 e SaldoInsuficiente se valor > saldo. No main, teste os dois casos e capture cada exceção separadamente, imprimindo mensagens distintas.

    Resolver
  3. 3
    Desafio

    Garantia forte com noexcept e rollback

    Implemente uma classe Buffer que mantém um std::vector<int> e oferece adicionar(int). A operação deve ter garantia forte: se a alocação falhar (simule com throw std::bad_alloc quando o valor for negativo), o buffer deve permanecer inalterado. Marque size() e operator[] como noexcept. No main, demonstre que após uma falha o buffer continua com os dados originais.

    Resolver

Continue estudando