Médio C++

Hierarquia de exceções personalizada

Enunciado

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.

Requisitos

  • SaldoInsuficiente deve herdar de std::runtime_error e ValorInvalido de std::logic_error.
  • A função sacar deve lançar a exceção correta para cada situação.
  • O main deve ter catch separados para cada tipo, imprimindo "Erro de saldo: ..." e "Erro de valor: ...".

Código inicial

#include <iostream>
#include <stdexcept>

// TODO: defina as classes de exceção

class Conta {
    double saldo_;
public:
    Conta(double saldo) : saldo_(saldo) {}
    void sacar(double valor) {
        // TODO: lance exceções conforme as regras
    }
    double saldo() const { return saldo_; }
};

int main() {
    // TODO: teste os casos
    return 0;
}

Saída esperada

Saldo inicial: 100
Saque de 150: Erro de saldo: saldo insuficiente
Saque de -10: Erro de valor: valor invalido
Saldo final: 100
Ver dica

Defina as classes herdando e passando a mensagem para o construtor da base: class SaldoInsuficiente : public std::runtime_error { public: SaldoInsuficiente() : std::runtime_error("saldo insuficiente") {} };.

Mostrar solução
#include <iostream>
#include <stdexcept>

// Exceção para saldo insuficiente (erro de execução)
class SaldoInsuficiente : public std::runtime_error {
public:
    SaldoInsuficiente() : std::runtime_error("saldo insuficiente") {}
};

// Exceção para valor inválido (erro de lógica)
class ValorInvalido : public std::logic_error {
public:
    ValorInvalido() : std::logic_error("valor invalido") {}
};

class Conta {
    double saldo_;
public:
    Conta(double saldo) : saldo_(saldo) {}

    void sacar(double valor) {
        if (valor <= 0.0)
            throw ValorInvalido{};          // erro de programação
        if (valor > saldo_)
            throw SaldoInsuficiente{};      // falha de execução
        saldo_ -= valor;
    }

    double saldo() const { return saldo_; }
};

int main() {
    Conta c(100.0);
    std::cout << "Saldo inicial: " << c.saldo() << '\n';

    try {
        c.sacar(150.0);
    } catch (const SaldoInsuficiente& e) {
        std::cout << "Saque de 150: Erro de saldo: " << e.what() << '\n';
    }

    try {
        c.sacar(-10.0);
    } catch (const ValorInvalido& e) {
        std::cout << "Saque de -10: Erro de valor: " << e.what() << '\n';
    }

    std::cout << "Saldo final: " << c.saldo() << '\n';
    return 0;
}

Passo a passo

  1. Definimos SaldoInsuficiente herdando de std::runtime_error e passando a mensagem para o construtor da base.
  2. Definimos ValorInvalido herdando de std::logic_error, também com mensagem fixa.
  3. A classe Conta armazena o saldo e expõe sacar.
  4. Em sacar, verificamos primeiro se o valor é inválido (<= 0) e lançamos ValorInvalido.
  5. Depois verificamos se o valor excede o saldo e lançamos SaldoInsuficiente.
  6. No main, cada chamada problemática é envolvida em um try com catch específico.
  7. O catch de SaldoInsuficiente imprime a mensagem de saldo; o de ValorInvalido, a de valor.
  8. O saldo permanece 100 porque as exceções impedem a subtração.

Por que funciona

A hierarquia permite capturar exceções específicas ou genéricas. Como ambas herdam de std::exception, um catch (const std::exception&) também as capturaria. A ordem dos catch importa: os mais derivados devem vir antes dos mais genéricos. Aqui usamos catch separados, então a ordem não é problema.

Erros comuns

  • Não passar a mensagem para a base: class SaldoInsuficiente : public std::runtime_error { public: SaldoInsuficiente() {} }; deixa what() vazio. Corrija chamando std::runtime_error("mensagem").
  • Capturar por valor: catch (SaldoInsuficiente e) causa slicing e perde o tipo derivado. Use referência.
  • Ordem errada dos catch: se capturar std::exception antes de SaldoInsuficiente, o específico nunca é alcançado.
  • Lançar após modificar o estado: se subtrair o saldo antes de verificar, o saldo fica inconsistente. Verifique antes de alterar.

Outra forma de resolver

Poderia usar uma única exceção base ContaErro e derivar as duas, capturando ContaErro e inspecionando um código. A separação por herança de runtime_error e logic_error é mais idiomática e expressa a natureza do erro.

Saída esperada

Saldo inicial: 100
Saque de 150: Erro de saldo: saldo insuficiente
Saque de -10: Erro de valor: valor invalido
Saldo final: 100