ProgramaciónDesarrollador Backend en C++

¿Cómo funciona el sistema de excepciones en C++ y en qué se diferencia de otros lenguajes? ¿Cómo diseñar correctamente el manejo de excepciones en aplicaciones industriales?

Supere entrevistas con el asistente de IA Hintsage

Respuesta

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:

  • A diferencia de Java o C#, las excepciones en C++ pueden ser de cualquier tipo (generalmente clases derivadas de std::exception).
  • En C++ no hay una especificación obligatoria de excepciones (como las excepciones comprobadas en Java), en su lugar, en C++11 y posteriores se introdujo el especificador noexcept.
  • Al lanzar una excepción, se llaman a los destructores de todos los objetos en la pila entre el punto de lanzamiento y el punto de manejo — es importante mantener RAII.

Diseño:

  • Lanza solo excepciones de errores “inesperados”, para otros casos utiliza códigos de retorno.
  • Captura por referencia constante — catch(const std::exception& e).
  • GNU recomienda lanzar objetos copiados, no punteros (evita memoria en el montón).

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; } }

Pregunta engañosa

¿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()

Ejemplos de errores reales debido a la falta de conocimiento de los matices del tema


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.