Manejo de excepciones en POO
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 81 de 104.
El manejo de excepciones en C++ proporciona una forma estructurada de detectar y responder a errores en tiempo de ejecución. En la programación orientada a objetos, las excepciones se integran de forma natural con las jerarquías de clases, lo que permite crear tipos de error significativos que contienen información contextual sobre lo que salió mal.
El mecanismo básico utiliza tres palabras clave: try, throw y catch. El código que podría fallar se coloca en un bloque try, los errores se señalizan con throw y los bloques catch manejan tipos de excepciones específicos:
#include <iostream>
#include <stdexcept>
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount account(100.0);
try {
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Balance: " << account.getBalance() << "\n";
}La biblioteca estándar de C++ proporciona una jerarquía de clases de excepción con raíz en std::exception. Las clases derivadas comunes incluyen std::runtime_error para problemas de tiempo de ejecución y std::logic_error para errores de programación. Todas las excepciones estándar proporcionan un método what() que devuelve un mensaje de error.
Cuando se lanza una excepción, la pila se desenrolla: se llama a los destructores de todos los objetos locales mientras el programa busca un bloque catch coincidente. Esto garantiza una limpieza adecuada incluso cuando ocurren errores, razón por la cual RAII y el manejo de excepciones funcionan tan bien juntos.
Desafío
FácilVamos a construir una calculadora de división segura que demuestre el manejo de excepciones en un contexto orientado a objetos. Crearás una clase Calculator que realice operaciones de división y lance excepciones significativas cuando algo salga mal, para luego manejar esas excepciones de manera adecuada en tu programa principal.
Organizarás tu código en tres archivos:
Calculator.h: Define tu claseCalculatorque realiza operaciones matemáticas con un manejo de errores adecuado.Tu
Calculatordebe tener un métododivide(double numerator, double denominator)que devuelva el resultado de la división. Sin embargo, si el denominador es cero, debe lanzar unstd::runtime_errorcon el mensaje"Division by zero". Si el numerador o el denominador son negativos, debe lanzar unstd::invalid_argumentcon el mensaje"Negative values not allowed".También incluye un método
safeSqrt(double value)que devuelva la raíz cuadrada del valor. Si el valor es negativo, lanza unstd::runtime_errorcon el mensaje"Cannot compute square root of negative number". Necesitarás incluir<cmath>para la funciónsqrt.Calculator.cpp: Implementa los métodos declarados en tu archivo de cabecera. Asegúrate de incluir los encabezados necesarios (<stdexcept>y<cmath>) y verifica las condiciones antes de realizar las operaciones.main.cpp: Lee tres entradas (cada una en una línea separada):- Tipo de operación: ya sea
"divide"o"sqrt" - Primer número (double)
- Segundo número (double) — solo se usa para la división, se ignora para sqrt
Crea un objeto
Calculatory realiza la operación solicitada dentro de un bloque try. Si la operación tiene éxito, imprime"Result: "seguido del valor. Si se captura una excepción, imprime"Error: "seguido del mensaje que proviene del métodowhat()de la excepción.Después del bloque try-catch (independientemente de si ocurrió una excepción), imprime
"Calculation complete"para demostrar que el programa continúa ejecutándose después de manejar las excepciones.- Tipo de operación: ya sea
Por ejemplo, con las entradas "divide", 10 y 2:
Result: 5
Calculation completeCon las entradas "divide", 10 y 0:
Error: Division by zero
Calculation completeCon las entradas "divide", -5 y 2:
Error: Negative values not allowed
Calculation completeCon las entradas "sqrt", 16 y 0:
Result: 4
Calculation completeCon las entradas "sqrt", -9 y 0:
Error: Cannot compute square root of negative number
Calculation completeObserva cómo el programa maneja los errores con elegancia al capturar excepciones y mostrar mensajes significativos, para luego continuar con la ejecución. Este patrón mantiene limpio el manejo de errores y permite que la lógica principal se centre en el flujo normal de ejecución.
Hoja de referencia
El manejo de excepciones en C++ utiliza tres palabras clave: try, throw y catch. El código que podría fallar se coloca en un bloque try, los errores se señalan con throw y los bloques catch manejan tipos de excepciones específicos:
try {
// Código que podría lanzar una excepción
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}Lanzar excepciones desde métodos de clase:
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}La biblioteca estándar de C++ proporciona clases de excepción con raíz en std::exception. Los tipos comunes incluyen:
std::runtime_error- para problemas en tiempo de ejecuciónstd::logic_error- para errores de programaciónstd::invalid_argument- para argumentos no válidos
Todas las excepciones estándar proporcionan un método what() que devuelve un mensaje de error.
Cuando se lanza una excepción, la pila se desenrolla (stack unwinding): se llama a los destructores de todos los objetos locales mientras el programa busca un bloque catch coincidente, lo que garantiza una limpieza adecuada.
Incluya <stdexcept> para usar las clases de excepción estándar.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Calculator.h"
using namespace std;
int main() {
// Leer las entradas
string operation;
double num1, num2;
cin >> operation;
cin >> num1;
cin >> num2;
// Crear el objeto Calculator
Calculator calc;
// TODO: Usar un bloque try-catch para realizar la operación
// - Si operation es "divide", llamar a calc.divide(num1, num2)
// - Si operation es "sqrt", llamar a calc.safeSqrt(num1)
// - En caso de éxito, imprimir "Result: " seguido del resultado
// - Capturar las excepciones e imprimir "Error: " seguido del mensaje de la excepción (usar what())
// - Después del try-catch, imprimir siempre "Calculation complete"
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico