Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 FileException que herede de std::exception. Debe almacenar un mensaje de error y sobrescribir el método what() con el especificador noexcept.

    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 clase FileProcessor que simule operaciones de archivos y lance tus excepciones personalizadas.

    Tu FileProcessor debe tener un método openFile(const std::string& filename) que simule la apertura de un archivo:

    • Si el nombre del archivo contiene "missing", lanza una FileNotFoundException.
    • Si el nombre del archivo contiene "locked", lanza una PermissionDeniedException.
    • De lo contrario, devuelve la cadena "Successfully opened: " + el nombre del archivo.
  • main.cpp: Lee un único nombre de archivo de la entrada.

    Crea un FileProcessor e intenta abrir el archivo. Usa un bloque try-catch con manejadores ordenados del más específico al más general:

    1. Captura FileNotFoundException e imprime: "File Error: " seguido del mensaje de la excepción.
    2. Captura PermissionDeniedException e imprime: "Access Error: " seguido del mensaje de la excepción.
    3. Captura FileException e 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().

Por ejemplo, con la entrada report.txt:

Successfully opened: report.txt

Con la entrada missing_data.csv:

File Error: File not found: missing_data.csv

Con la entrada locked_config.ini:

Access Error: Permission denied: locked_config.ini

Este 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;
}
quiz iconPonte a prueba

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