Saltar al contenido
Portada » C/C++ » 12. Manejo de Excepciones en C++

12. Manejo de Excepciones en C++

Manejo de Excepciones en C/C++. En programación los errores suceden: intentos de dividir por cero, acceso a archivos inexistentes, operaciones sobre estructuras vacías, etc. Ignorar estos errores o no gestionarlos correctamente puede provocar que una aplicación se bloquee o arroje resultados incorrectos.

Para gestionar estos casos de forma segura y hacer los programas sean robustos y carentes de errores, C++ proporciona un mecanismo conocido como manejo de excepciones.

Este sistema permite a los programas:

  • Detectar errores en tiempo de ejecución.
  • Capturarlos de forma controlada.
  • Actuar en consecuencia sin interrumpir bruscamente la ejecución del resto del programa.

En este capítulo exploraremos:

  • La sintaxis básica del manejo de excepciones (try, catch, throw).
  • Cómo capturar excepciones específicas y tratar diferentes tipos de errores de manera diferenciada.
  • Cómo crear nuestras propias excepciones personalizadas.

Empezamos!!!!

12.1 try, catch y throw en C++

¿Qué son?

El manejo de excepciones en C++ se basa en tres palabras clave fundamentales:

  • try: Indica el bloque de código que puede producir un error (una excepción).
  • throw: Utilizado para lanzar una excepción (error) cuando ocurre un error.
  • catch: Define el bloque que captura y maneja la excepción lanzada (acción).

Estructura básica

try {
// Código que puede lanzar una excepción
throw valor_o_objeto; // Se lanza una excepción
}
catch (tipo valor) {
// Código para manejar la excepción
}

Cuando se lanza una excepción con throw, el flujo de ejecución salta automáticamente al bloque catch correspondiente, y el resto del bloque try ya no se ejecuta.

Veamoslo mejor con un ejemplo.

#include <iostream>
using namespace std;

int main() {
try {
int divisor = 0;
if (divisor == 0)
throw "Error: división por cero";
int resultado = 10 / divisor;
cout << "Resultado: " << resultado << endl;
}
catch (const char* mensaje) {
cout << "Se ha capturado una excepción: " << mensaje << endl;
}

return 0;
}

¿Qué tenemos aquí?

  • Dentro del bloque try, se detecta que divisor == 0.
  • En lugar de hacer la división y provocar un error en tiempo de ejecución, se lanza una excepción con throw "Error: división por cero".
  • El control pasa automáticamente al bloque catch, que recibe un const char* (una cadena de texto).
  • Se imprime el mensaje de error sin que el programa se cierre abruptamente.

Lanzar diferentes tipos de excepciones

C++ nos da la posibilidad de lanzar distintos tipos de valores según la situación:

throw 404;           // Entero
throw 3.14; // Número decimal
throw "Error"; // Cadena de texto
throw runtime_error("Archivo no encontrado"); // Objeto de clase

Y, para manejarlos, debes usar un catch que coincida:

catch (int codigo) { ... }
catch (double valor) { ... }
catch (const char* mensaje) { ... }
catch (const exception& e) { ... }

catch genérico

Cuando no sabemos qué tipo de excepción puede lanzarse, podemos usar un bloque genérico:

catch (...) {
cout << "Se ha producido una excepción desconocida." << endl;
}

Ejemplo

#include <iostream>
#include <stdexcept>
using namespace std;

void dividir(int a, int b) {
if (b == 0)
throw runtime_error("No se puede dividir por cero");
cout << "Resultado: " << a / b << endl;
}

int main() {
try {
dividir(10, 0);
}
catch (const runtime_error& e) {
cout << "Error: " << e.what() << endl;
}

return 0;
}
  • Se usa una función que puede lanzar una excepción.
  • runtime_error es una clase de excepción estándar de C++.
  • La función e.what() devuelve el mensaje de error que se pasó en el throw.

Recuerda

Es importante utilizar el manejo de execpciones con try, catch, throw ya que

  • Evita que el programa se bloquee inesperadamente.
  • Separa claramente el código normal del código de gestión de errores.
  • Mejora la robustez y mantenibilidad del software.

12.2 Captura de Excepciones Específicas

En C++, cuando se lanza una excepción mediante throw, podemos capturarla en bloques catch que coincidan con su tipo exacto. Esto nos permite reaccionar de manera diferente ante distintos tipos de errores, escribiendo código controlado.

¿Por qué capturar tipos específicos?

La captura de tipos específicos nos permite:

  • Identificar el origen y naturaleza del error con mayor exactitud.
  • Tomar decisiones diferentes según el tipo de excepción lanzada.
  • Mantener el programa más robusto y organizado.

Sintaxis

try {
// Código que puede lanzar diferentes tipos de excepciones
}
catch (int errorCode) {
// Manejo si se lanza un int
}
catch (const char* mensaje) {
// Manejo si se lanza un const char*
}
catch (const exception& e) {
// Manejo para errores estándar de C++
}

Veamos un ejemplo

#include <iostream>
#include <stdexcept>
using namespace std;

void lanzarError(int tipo) {
if (tipo == 1)
throw 404;
else if (tipo == 2)
throw "Error de tipo cadena";
else if (tipo == 3)
throw runtime_error("Excepción estándar");
}

int main() {
try {
lanzarError(3); // Prueba con 1, 2 o 3
}
catch (int codigo) {
cout << "Error numérico capturado: " << codigo << endl;
}
catch (const char* mensaje) {
cout << "Error de texto capturado: " << mensaje << endl;
}
catch (const exception& e) {
cout << "Excepción estándar capturada: " << e.what() << endl;
}

return 0;
}

En este código:

  • lanzarError lanza un tipo distinto de excepción según el parámetro.
  • El bloque main contiene varios catch, cada uno preparado para un tipo específico:
    • int: para códigos numéricos.
    • const char*: para mensajes de texto.
    • const exception&: para excepciones estándar como runtime_error, logic_error, etc.
  • Se ejecutará únicamente el bloque catch que coincida con el tipo lanzado.

¿Qué pasa si no hay coincidencia?

Si se lanza una excepción y no existe un bloque catch que coincida con su tipo, el programa termina abruptamente (lanza una excepción no manejada).

Por eso es recomendable, en ciertos casos, tener un bloque genérico al final:

catch (...) {
cout << "Excepción no identificada capturada." << endl;
}

Este actúa como un «filtro de seguridad» para cualquier excepción no prevista.

Truco

También es posible re-lanzar una excepción si un catch no puede manejarla del todo:

catch (const exception& e) {
cout << "Capturado: " << e.what() << endl;
throw; // Re-lanza la misma excepción
}

12.3 Excepciones Personalizadas

En C++, además de capturar excepciones predefinidas como std::runtime_error o std::out_of_range, también podemos definir nuestras propias clases de excepción. Esto es útil cuando queremos representar errores específicos de nuestra aplicación o sistema con información particular y más concreta.

Casosn en los que crear excepciones personalizadas:

  • Para representar errores concretos de nuestro dominio (por ejemplo, ArchivoNoEncontrado, SaldoInsuficiente, etc.).
  • Para añadir información adicional al error (como código de error, contexto, fecha…).
  • Para hacer el código más claro y autoexplicativo.

Cómo crear una excepción personalizada

Para crear una excepción propia:

  1. Creamos una clase que herede de std::exception (u otra clase de excepción).
  2. Sobrescribimos el método what() para dar un mensaje descriptivo.

Veamos como hacerlo.

#include <iostream>
#include <exception>
using namespace std;

class MiExcepcion : public exception {
public:
const char* what() const noexcept override {
return "¡Se produjo un error personalizado!";
}
};

int main() {
try {
throw MiExcepcion();
}
catch (const MiExcepcion& e) {
cout << "Excepción capturada: " << e.what() << endl;
}

return 0;
}
  • MiExcepcion hereda de std::exception.
  • Sobrescribimos what() para devolver un mensaje propio.
  • En main, lanzamos la excepción con throw MiExcepcion().
  • Se captura en un catch que reconoce el tipo exacto.

Para añadir información adicional lo haríamos así:

#include <iostream>
#include <exception>
#include <string>
using namespace std;

class ArchivoNoEncontrado : public exception {
private:
string mensaje;
public:
ArchivoNoEncontrado(const string& nombreArchivo) {
mensaje = "Archivo no encontrado: " + nombreArchivo;
}

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

int main() {
try {
throw ArchivoNoEncontrado("datos.txt");
}
catch (const ArchivoNoEncontrado& e) {
cout << "Error: " << e.what() << endl;
}

return 0;
}

Si te fijas, con este enfoque

  • Podemos crear excepciones con nombres significativos y comportamiento personalizado.
  • Podemos pasar parámetros (nombre del archivo, código, etc.).
  • Se integran perfectamente en el sistema de manejo de excepciones con try/catch.

Como recomendaciones te sugirero que.

  • Hereda de std::exception o una de sus derivadas.
  • Siempre sobrescribe what() y marca la función como noexcept.
  • Puedes crear una jerarquía de excepciones propias si tu programa es grande.

Combinación con excepciones estándar

También puedes lanzar excepciones estándar personalizadas usando las clases de la STL:

hrow std::invalid_argument("Parámetro no válido");
throw std::out_of_range("Índice fuera de rango");

Estas ya están preparadas para uso general y tienen sus propios mensajes.

Conclusión

Hasta aquí este breve capítulo donde hemos aprendido a gestionar errores en tiempo de ejecución utilizando el sistema de excepciones de C++. Vimos cómo:

  • Usar los bloques try, catch y throw para capturar y manejar errores.
  • Capturar excepciones específicas, como aquellas lanzadas por la biblioteca estándar (std::runtime_error, std::invalid_argument, etc.).
  • Crear excepciones personalizadas mediante clases propias que heredan de std::exception, permitiéndonos generar errores más descriptivos y adaptados a nuestras aplicaciones.

Vamos ahora a practicar.

Ejercicio 1: División segura
Enunciado:
Crea una función que reciba dos enteros y devuelva el resultado de dividir el primero entre el segundo. Si el divisor es cero, lanza una excepción y captúrala en la función main, mostrando un mensaje adecuado al usuario.

Código:

#include <iostream>
#include <stdexcept>

double dividir(int a, int b) {
if (b == 0)
throw std::runtime_error("Error: división por cero no permitida.");
return static_cast<double>(a) / b;
}

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

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

return 0;
}

Explicación:
La función dividir lanza una excepción std::runtime_error si el divisor es cero. En main, se captura y muestra el mensaje de error. Se evita que el programa se cierre abruptamente.

Ejercicio 2: Acceso seguro a un vector
Enunciado:
Crea un programa que almacene 5 enteros en un std::vector y permita al usuario acceder a una posición concreta. Si el usuario introduce un índice fuera de rango, lanza y captura la excepción apropiada.

Código:

cppCopyEdit#include <iostream>
#include <vector>
#include <stdexcept>

int main() {
    std::vector<int> numeros = {1, 2, 3, 4, 5};
    int indice;

    std::cout << "Introduce un índice entre 0 y 4: ";
    std::cin >> indice;

    try {
        std::cout << "Número en la posición " << indice << ": " << numeros.at(indice) << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << "Excepción capturada: " << e.what() << std::endl;
    }

    return 0;
}

Explicación:
El método at() del vector lanza automáticamente una excepción std::out_of_range si el índice es inválido. Al capturarla, evitamos que el programa se bloquee y mostramos una advertencia.

Ejercicio 3: Excepción personalizada para edad no válida
Enunciado:
Crea una clase EdadInvalidaException que se use para lanzar un error cuando se intente registrar a una persona con una edad negativa o mayor a 130 años.

SCódigo:

cppCopyEdit#include <iostream>
#include <exception>
#include <string>

class EdadInvalidaException : public std::exception {
    std::string mensaje;
public:
    EdadInvalidaException(int edad) {
        mensaje = "Edad inválida: " + std::to_string(edad);
    }
    const char* what() const noexcept override {
        return mensaje.c_str();
    }
};

void registrarPersona(int edad) {
    if (edad < 0 || edad > 130)
        throw EdadInvalidaException(edad);

    std::cout << "Persona registrada con edad: " << edad << std::endl;
}

int main() {
    try {
        registrarPersona(150);
    } catch (const EdadInvalidaException& e) {
        std::cerr << "Excepción personalizada capturada: " << e.what() << std::endl;
    }

    return 0;
}

Explicación:
Se define una clase de excepción personalizada que hereda de std::exception. Cuando se detecta una edad inválida, se lanza esta excepción con un mensaje detallado. La función main la captura y muestra el error.

Logo C++

Bibliografía del tutorial de C/C++.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *