DMarlon Studio

Lección 17 · 17 de 20

Jerarquías de excepciones · C++

Cree sus propias excepciones en C++ derivando de std::runtime_error, con datos extra, y captúrelas de la más concreta a la más general.

C++ · Jerarquías de excepciones en DMarlon Studio: el código del ejemplo y su salida en la terminal

Las excepciones propias se crean heredando de las estándar. AppError deriva de std::runtime_error y, con using std::runtime_error::runtime_error;, hereda su constructor, que recibe el mensaje que luego devuelve what(). Todas las excepciones de la aplicación serán AppError y, por tanto, también std::exception.

Las derivadas añaden información útil: ValidationError guarda el campo que está mal (field) y PaymentError, el código que devuelve el banco (code). Así, quien captura el error no tiene que adivinarlo leyendo el mensaje.

checkout valida el pedido y lanza la excepción que corresponde: un correo sin @, un carrito vacío o un importe que el banco rechaza. Si todo va bien, cobra y escribe «OK». El bucle recorre los pedidos con *structured bindings* ([email, amount]) y cada try atiende uno, así que un fallo no detiene a los demás.

Los catch se prueban en orden y se queda el primero que encaja. Por eso van de lo más concreto a lo más general: si catch (const AppError&) fuera el primero, se llevaría también los errores de validación y de pago. Capture siempre por referencia `const`: evita copias y el recorte del objeto, y conserva el tipo real.

Código · main.cpp

#include <iostream>#include <stdexcept>#include <string>#include <utility>#include <vector>// A family of errors: all of them are AppError, and AppError is a runtime_error.struct AppError : std::runtime_error { using std::runtime_error::runtime_error; };struct ValidationError : AppError {    std::string field;                                  // extra data about the error    ValidationError(const std::string& f, const std::string& msg) : AppError(msg), field(f) {}};struct PaymentError : AppError {    int code;    explicit PaymentError(int c) : AppError("payment declined"), code(c) {}};void checkout(const std::string& email, double amount) {    if (email.find('@') == std::string::npos) throw ValidationError("email", "missing @");    if (amount <= 0) throw AppError("the cart is empty");    if (amount > 1000) throw PaymentError(51);    std::cout << "OK: " << amount << " EUR charged to " << email << "\n";}int main() {    std::vector<std::pair<std::string, double>> orders = {        {"ana@mail.com", 59.9}, {"luis.mail.com", 20}, {"eva@mail.com", 1500}, {"leo@mail.com", 0}};    for (const auto& [email, amount] : orders) {        try { checkout(email, amount); }     // the catches go from most to least specific        catch (const ValidationError& e) { std::cout << "Invalid " << e.field << ": " << e.what() << "\n"; }        catch (const PaymentError& e) { std::cout << "Bank code " << e.code << ": " << e.what() << "\n"; }        catch (const AppError& e) { std::cout << "Cannot check out: " << e.what() << "\n"; }    }}

Salida · c++ -std=c++17 main.cpp -o main && ./main

OK: 59.9 EUR charged to ana@mail.com
Invalid email: missing @
Bank code 51: payment declined
Cannot check out: the cart is empty

Para recordar

  • Derive sus excepciones de std::runtime_error (o de otra estándar).
  • using Base::Base; hereda los constructores de la base.
  • Las excepciones pueden llevar datos además del mensaje.
  • Ordene los catch de la clase más derivada a la más general.
  • Capture por const&, nunca por valor.

Ejercicio

Cree StockError, derivada de AppError, con el nombre del producto agotado. Lánzela en checkout cuando el correo sea ana@mail.com y añada su catch en el lugar correcto.