El sistema de excepciones en C++ se basa en el mecanismo try-catch y la palabra clave throw. Cuando ocurre una situación excepcional (por ejemplo, un error de recurso, violación de invariantes), se lanza una excepción dentro del bloque try a través del operador throw. Las operaciones para encontrar el controlador adecuado (catch) se realizan mostrando la pila hasta el primer tipo coincidente.
Detalles importantes:
std::exception).noexcept.Diseño:
catch(const std::exception& e).Ejemplo de código:
#include <iostream> #include <stdexcept> void mayFail(bool fail) { if (fail) throw std::runtime_error("Error de proceso"); } int main() { try { mayFail(true); } catch (const std::exception& ex) { std::cout << "Excepción capturada: " << ex.what() << std::endl; } }
¿Qué sucederá si se lanza una excepción desde un destructor mientras se transmite otra excepción hacia arriba en la pila?
Respuesta: La aplicación terminará de forma abrupta a través de std::terminate(), porque durante el manejo de una excepción no se permite generar una nueva excepción (doble excepción), de lo contrario, se interrumpe el desensamblaje de la pila.
Ejemplo:
struct CrashOnDestruct { ~CrashOnDestruct() noexcept(false) { throw std::runtime_error("Error en el destructor!"); } }; void func() { CrashOnDestruct obj; throw std::logic_error("Manejo de error"); } int main() { try { func(); } catch (...) { } } // Terminará a través de std::terminate()
Historia
Dentro de una gran aplicación financiera, se lanzaban excepciones desde el destructor de una clase de envoltura sobre una base de datos. Una de las transacciones críticas al ocurrir un error generaba una excepción, al salir de unwind se llamaba al destructor, que también lanzaba un error. Toda la aplicación terminaba abruptamente, perdiendo el trabajo de los usuarios. Los desarrolladores tuvieron que reemplazar urgentemente el throw en los destructores por registros y un manejo adecuado.
Historia
Un microservicio manejaba excepciones del tipo: catch(std::exception). Uno de los hilos lanzaba excepciones de tipos de usuario (no heredados de std::exception). Tales errores no eran capturados, resultando en una desconexión inesperada y fuga de memoria.
Historia
La falta de especificador noexcept en los métodos de movimiento de los contenedores condujo a que la biblioteca estándar utilizara la copia lenta en lugar de la rápida operación de movimiento; un fracaso significativo en el rendimiento solo se detectó durante las pruebas de carga.