Jerarquías de excepciones personalizadas
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 82 de 104.
Si bien la biblioteca estándar proporciona clases de excepciones útiles como std::runtime_error, las aplicaciones del mundo real a menudo se benefician de crear jerarquías de excepciones personalizadas. Al definir tus propias clases de excepciones que heredan de excepciones estándar, puedes crear tipos de error específicos del dominio que incorporen contexto adicional y permitan un manejo de errores más preciso.
Una clase de excepción personalizada normalmente hereda de std::exception o de una de sus clases derivadas y sobrescribe el método what():
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& query)
: DatabaseException("Invalid query: " + query) {}
};Esta jerarquía permite que los bloques catch gestionen excepciones en diferentes niveles de especificidad:
try {
// operaciones de base de datos...
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}El especificador noexcept en what() es importante: garantiza que el método no lanzará excepciones, lo cual es requerido por la interfaz de la clase base. Ordena tus bloques catch desde los más específicos hasta los más general, ya que C++ utiliza el primer controlador coincidente.
Desafío
FácilConstruyamos un sistema de procesamiento de archivos con una jerarquía de excepciones personalizada que gestione distintos tipos de errores relacionados con archivos. Crearás excepciones específicas del dominio que hereden de una clase de excepción base, lo que permitirá gestionar los errores con precisión en distintos niveles de especificidad.
Organizarás tu código en tres archivos:
FileExceptions.h: Define tu jerarquía de excepciones personalizada para las operaciones con archivos.Crea una clase base
FileExceptionque herede destd::exception. Debe almacenar un mensaje de error y sobrescribir el métodowhat()con el especificadornoexcept.Después crea dos clases de excepción derivadas:
FileNotFoundException: recibe un nombre de archivo y crea el mensaje:File not found: [filename]PermissionDeniedException: recibe un nombre de archivo y crea el mensaje:Permission denied: [filename]
Cada clase derivada debe llamar al constructor de su clase principal con el mensaje correspondiente.
FileProcessor.h: Define una claseFileProcessorque simule operaciones con archivos y lance tus excepciones personalizadas.Tu
FileProcessordebe tener un métodoopenFile(const std::string& filename)que simule la apertura de un archivo:- Si el nombre de archivo contiene
missing, lanza unaFileNotFoundException - Si el nombre de archivo contiene
locked, lanza unaPermissionDeniedException - De lo contrario, devuelve la cadena
Successfully opened: [filename]
- Si el nombre de archivo contiene
main.cpp: Lee un único nombre de archivo de la entrada.Crea un
FileProcessore intenta abrir el archivo. Usa un bloque try-catch con los controladores ordenados de los más específicos a los más generales:- Captura
FileNotFoundExceptione imprime:File Error: [message] - Captura
PermissionDeniedExceptione imprime:Access Error: [message] - Captura
FileExceptione imprime:General File Error: [message]
Si no se lanza ninguna excepción, imprime el mensaje de éxito devuelto por
openFile().- Captura
Por ejemplo, con la entrada report.txt:
Successfully opened: report.txtCon la entrada missing_data.csv:
File Error: File not found: missing_data.csvCon la entrada locked_config.ini:
Access Error: Permission denied: locked_config.iniEste desafío demuestra cómo las jerarquías de excepciones personalizadas permiten gestionar los errores con el nivel de detalle adecuado: capturando excepciones específicas cuando necesitas un manejo preciso, o capturando la excepción base cuando quieres gestionar todos los errores de archivos de manera uniforme.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"
using namespace std;
int main() {
string filename;
cin >> filename;
FileProcessor processor;
// TODO: Usa un bloque try-catch para manejar excepciones
// Ordena los manejadores de más específico a más general:
// 1. Catch FileNotFoundException - imprime: "File Error: [message]"
// 2. Catch PermissionDeniedException - imprime: "Access Error: [message]"
// 3. Catch FileException - imprime: "General File Error: [message]"
// Si no hay excepción, imprime el mensaje de éxito de openFile()
try {
// TODO: Llama a processor.openFile() y maneja el resultado
}
// TODO: Añade bloques catch en orden de más específico a más general
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éricoPractica por tu cuenta: Compilador de C++ online