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 excepción útiles como std::runtime_error, las aplicaciones del mundo real a menudo se benefician de la creación de jerarquías de excepciones personalizadas. Al definir sus propias clases de excepción que heredan de las excepciones estándar, puede crear tipos de error específicos del dominio que aporten 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 manejen 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 del más específico al más general, ya que C++ utiliza el primer manejador que coincida.
Desafío
FácilVamos a construir un sistema de procesamiento de archivos con una jerarquía de excepciones personalizada que maneje diferentes tipos de errores relacionados con archivos. Crearás excepciones específicas del dominio que heredan de una clase de excepción base, lo que permitirá un manejo de errores preciso en diferentes niveles de especificidad.
Organizarás tu código en tres archivos:
FileExceptions.h: Define tu jerarquía de excepciones personalizada para operaciones de archivos.Crea una clase base
FileExceptionque herede destd::exception. Debe almacenar un mensaje de error y sobrescribir el métodowhat()con el especificadornoexcept.Luego, crea dos clases de excepción derivadas:
FileNotFoundException— recibe un nombre de archivo y crea un mensaje:"File not found: "+ el nombre del archivo.PermissionDeniedException— recibe un nombre de archivo y crea un mensaje:"Permission denied: "+ el nombre del archivo.
Cada clase derivada debe llamar al constructor de su padre con el mensaje apropiado.
FileProcessor.h: Define una claseFileProcessorque simule operaciones de 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 del archivo contiene
"missing", lanza unaFileNotFoundException. - Si el nombre del archivo contiene
"locked", lanza unaPermissionDeniedException. - De lo contrario, devuelve la cadena
"Successfully opened: "+ el nombre del archivo.
- Si el nombre del 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 manejadores ordenados del más específico al más general:- Captura
FileNotFoundExceptione imprime:"File Error: "seguido del mensaje de la excepción. - Captura
PermissionDeniedExceptione imprime:"Access Error: "seguido del mensaje de la excepción. - Captura
FileExceptione imprime:"General File Error: "seguido del mensaje de la excepción.
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 te permiten manejar errores con el nivel de detalle adecuado: capturando excepciones específicas cuando necesitas un manejo preciso, o capturando la excepción base cuando deseas manejar todos los errores de archivo de manera uniforme.
Hoja de referencia
Las clases de excepción personalizadas heredan de std::exception o sus clases derivadas y sobrescriben 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(); }
};Cree jerarquías de excepciones heredando de excepciones base personalizadas:
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};Maneje las excepciones en diferentes niveles de especificidad ordenando los bloques catch del más específico al más general:
try {
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() garantiza que el método no lanzará excepciones, lo cual es requerido por la interfaz de la clase base.
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 del más específico al más general:
// 1. Captura FileNotFoundException - imprime: "File Error: [message]"
// 2. Captura PermissionDeniedException - imprime: "Access Error: [message]"
// 3. Captura FileException - imprime: "General File Error: [message]"
// Si no hay excepciones, imprime el mensaje de éxito de openFile()
try {
// TODO: Llama a processor.openFile() y maneja el resultado
}
// TODO: Añade bloques catch en orden del más específico al 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érico