Skip to content

Capítulo 10: Manejo de Excepciones ​

10.1 Nivel Introductorio ​

Concepto de excepción ​

En programación, una excepción es un evento que ocurre durante la ejecución de un programa y que interrumpe el flujo normal de instrucciones, generalmente debido a un error o situación inesperada. Las excepciones permiten manejar estos errores de manera controlada, evitando que el programa termine abruptamente y proporcionando información sobre el problema ocurrido.

Las excepciones en C++ son mecanismos que permiten:

  • Detectar errores en tiempo de ejecución.
  • Transmitir información sobre el error.
  • Manejar el error en un lugar apropiado del código.

Bloques try, catch, throw ​

El manejo de excepciones en C++ se realiza mediante tres componentes clave:

  • throw: Se utiliza para lanzar una excepción.
  • try: Define un bloque de código en el que pueden ocurrir excepciones.
  • catch: Captura y maneja las excepciones lanzadas dentro del bloque try.

Uso básico de try, catch, throw ​

Sintaxis:

cpp
try {
    // Código que puede lanzar una excepción
} catch (TipoDeExcepción e) {
    // Código para manejar la excepción
}

Ejemplo sencillo:

cpp
#include <iostream>

int main() {
    int divisor, dividendo, resultado;

    std::cout << "Ingresa el dividendo: ";
    std::cin >> dividendo;
    std::cout << "Ingresa el divisor: ";
    std::cin >> divisor;

    try {
        if (divisor == 0) {
            throw "Error: División por cero.";
        }
        resultado = dividendo / divisor;
        std::cout << "Resultado: " << resultado << std::endl;
    } catch (const char* mensaje) {
        std::cerr << mensaje << std::endl;
    }

    return 0;
}

Explicación:

  • Se solicita al usuario ingresar el dividendo y el divisor.
  • Se verifica si el divisor es cero. Si es así, se lanza una excepción usando throw.
  • El bloque try contiene el código que puede lanzar una excepción.
  • El bloque catch captura la excepción y maneja el error mostrando un mensaje al usuario.

Tipos de excepciones ​

Las excepciones en C++ pueden ser de cualquier tipo, pero es común usar tipos derivados de std::exception o tipos predefinidos como std::runtime_error, std::out_of_range, etc.

Ejemplo usando std::exception:

cpp
#include <iostream>
#include <exception>

int main() {
    try {
        throw std::runtime_error("Ocurrió un error de ejecución.");
    } catch (std::exception& e) {
        std::cerr << "Excepción capturada: " << e.what() << std::endl;
    }

    return 0;
}

Nota: El método what() devuelve una cadena con información sobre la excepción.


10.2 Nivel Intermedio ​

Excepciones personalizadas ​

Es posible definir nuestras propias clases de excepción derivando de std::exception o alguna de sus subclases. Esto permite crear excepciones que representen errores específicos de nuestra aplicación.

Ejemplo de excepción personalizada:

cpp
#include <iostream>
#include <exception>

class MiExcepcion : public std::exception {
private:
    const char* mensaje;
public:
    MiExcepcion(const char* msg) : mensaje(msg) {}

    const char* what() const noexcept override {
        return mensaje;
    }
};

int main() {
    try {
        throw MiExcepcion("Mi excepción personalizada ha ocurrido.");
    } catch (MiExcepcion& e) {
        std::cerr << "Excepción capturada: " << e.what() << std::endl;
    }

    return 0;
}

Explicación:

  • Se define la clase MiExcepcion que hereda de std::exception.
  • Se sobreescribe el método what() para proporcionar un mensaje de error personalizado.
  • Al lanzar y capturar la excepción, podemos manejar errores específicos de nuestra aplicación.

Propagación y manejo de excepciones ​

Las excepciones pueden propagarse a través de las funciones llamadas, hasta que son capturadas por un bloque catch. Esto permite manejar errores en un nivel superior de la aplicación.

Ejemplo de propagación de excepciones:

cpp
#include <iostream>

double dividir(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("Error: División por cero.");
    }
    return a / b;
}

int main() {
    double x = 10, y = 0;

    try {
        double resultado = dividir(x, y);
        std::cout << "Resultado: " << resultado << std::endl;
    } catch (std::exception& e) {
        std::cerr << "Excepción capturada en main: " << e.what() << std::endl;
    }

    return 0;
}

Explicación:

  • La función dividir puede lanzar una excepción si b es cero.
  • En main, se llama a dividir dentro de un bloque try.
  • Si se lanza una excepción, se captura en el bloque catch de main.

Múltiples bloques catch ​

Es posible tener múltiples bloques catch para manejar diferentes tipos de excepciones.

Ejemplo:

cpp
try {
    // Código que puede lanzar diferentes excepciones
} catch (const MiExcepcion& e) {
    // Manejo de MiExcepcion
} catch (const std::exception& e) {
    // Manejo de otras excepciones derivadas de std::exception
} catch (...) {
    // Manejo de cualquier otra excepción
}

Nota: El bloque catch (...) captura cualquier excepción, sin importar su tipo.


10.3 Nivel Avanzado ​

Buenas prácticas en manejo de excepciones ​

  • No abusar de las excepciones: Úsalas para condiciones verdaderamente excepcionales, no para el flujo normal del programa.
  • Lanzar objetos de excepción adecuados: Preferiblemente, lanzar objetos que heredan de std::exception.
  • Capturar excepciones por referencia: Para evitar copias innecesarias y mantener el polimorfismo.

Ejemplo:

cpp
try {
    // Código
} catch (const std::exception& e) {
    // Manejo
}
  • Limpiar recursos en caso de excepción: Asegúrate de que los recursos (memoria, archivos abiertos, etc.) se liberen adecuadamente.

Uso de noexcept y especificaciones de excepción ​

noexcept ​

La especificación noexcept indica que una función no lanzará excepciones. Esto puede ayudar al compilador en optimizaciones y también proporciona información al usuario de la función.

Sintaxis:

cpp
void funcion() noexcept {
    // Código que no lanza excepciones
}

noexcept con expresiones ​

Puedes usar una expresión booleana para indicar condicionalmente si una función es noexcept.

Ejemplo:

cpp
void funcion() noexcept(condicion) {
    // Código
}

noexcept en movimientos ​

Las operaciones de movimiento deberían ser noexcept para permitir optimizaciones en contenedores de la STL.

Ejemplo:

cpp
class MiClase {
public:
    MiClase(MiClase&& otra) noexcept {
        // Constructor de movimiento
    }
};

Excepciones en constructores y destructores ​

Excepciones en constructores ​

Si un constructor lanza una excepción, el objeto no se crea y se liberan los recursos ya adquiridos durante la construcción.

Ejemplo:

cpp
class Conexion {
public:
    Conexion(const std::string& url) {
        if (!conectar(url)) {
            throw std::runtime_error("No se pudo establecer la conexión.");
        }
    }
};

Excepciones en destructores ​

Es una mala práctica lanzar excepciones desde destructores, ya que si una excepción es lanzada durante el proceso de eliminación de objetos por otra excepción, esto puede llevar a terminar el programa abruptamente (std::terminate).

Recomendación:

  • No lanzar excepciones desde destructores.
  • Manejar internamente los errores en destructores.

Ejemplo:

cpp
class Archivo {
public:
    ~Archivo() {
        try {
            cerrar();
        } catch (...) {
            // Manejar el error internamente
        }
    }
};