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 bloquetry.
Uso básico de try, catch, throw
Sintaxis:
try {
// Código que puede lanzar una excepción
} catch (TipoDeExcepción e) {
// Código para manejar la excepción
}Ejemplo sencillo:
#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
trycontiene el código que puede lanzar una excepción. - El bloque
catchcaptura 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:
#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:
#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
MiExcepcionque hereda destd::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:
#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
dividirpuede lanzar una excepción sibes cero. - En
main, se llama adividirdentro de un bloquetry. - Si se lanza una excepción, se captura en el bloque
catchdemain.
Múltiples bloques catch
Es posible tener múltiples bloques catch para manejar diferentes tipos de excepciones.
Ejemplo:
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:
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:
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:
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:
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:
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:
class Archivo {
public:
~Archivo() {
try {
cerrar();
} catch (...) {
// Manejar el error internamente
}
}
};