Introducción: Por qué el manejo de errores es la base del código robusto en C++
Cualquier programa que interactúe con el mundo exterior (archivos, red, entrada del usuario) inevitablemente se encuentra con situaciones excepcionales. En C++, el mecanismo try/catch es la herramienta principal para gestionar estas situaciones de manera elegante y controlada. En este artículo, analizaremos el manejo de errores en C++ desde las construcciones básicas hasta las técnicas avanzadas, incluyendo best practices y trampas comunes.
1. Fundamentos: try, catch y throw
El mecanismo de excepciones en C++ se basa en tres palabras clave: try, catch y throw.
1.1. Bloque try y catch
El bloque try contiene el código que puede lanzar una excepción. El bloque catch captura esa excepción y la maneja.
#include <iostream>#include <stdexcept>
int main() { try { // Código que puede causar un error int age = -5; if (age < 0) { throw std::invalid_argument("La edad no puede ser negativa"); } std::cout << "Edad: " << age << std::endl; } catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; } return 0;}1.2. Tipos de excepciones
En C++ se pueden lanzar excepciones de cualquier tipo: números, cadenas, objetos de clase. Sin embargo, se recomienda usar las clases de excepción estándar de <stdexcept> o crear tus propias clases heredadas de std::exception.
- Excepciones estándar:
std::runtime_error,std::logic_error,std::out_of_range, etc. - Excepciones personalizadas: se crean mediante herencia de
std::exception.
class MyException : public std::exception {public: const char* what() const noexcept override { return "Mi propia excepción"; }};1.3. Múltiples bloques catch
Puedes capturar diferentes tipos de excepciones en bloques catch separados. El orden es importante: las excepciones más específicas deben ir primero.
try { // código} catch (const std::invalid_argument& e) { // manejo de invalid_argument} catch (const std::exception& e) { // manejo de todas las demás excepciones de std} catch (...) { // catch-all: captura todo lo que no se capturó arriba}2. Técnicas avanzadas: pila de llamadas y RAII
2.1. Desenrollado de la pila (Stack Unwinding)
Cuando se lanza una excepción, C++ destruye automáticamente todos los objetos locales en el camino desde el punto de throw hasta el catch correspondiente. Este proceso se llama desenrollado de la pila. Garantiza que se llamen los destructores de todos los objetos creados en los bloques try.
class Resource {public: Resource() { std::cout << "Recurso creado"; } ~Resource() { std::cout << "Recurso destruido"; }};
void riskyFunction() { Resource res; throw std::runtime_error("Algo salió mal");}
int main() { try { riskyFunction(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } // Salida: Recurso creado -> Recurso destruido -> Algo salió mal}2.2. RAII (Resource Acquisition Is Initialization)
RAII es un idioma clave de C++ que está estrechamente relacionado con el manejo de errores. La idea: la gestión de recursos (memoria, archivos, mutex) se vincula al tiempo de vida del objeto. El destructor libera automáticamente el recurso, incluso ante una excepción.
- Punteros inteligentes:
std::unique_ptr,std::shared_ptr