Manejo avanzado de errores en C++: de try/catch a prácticas industriales
El manejo de errores es una de las piedras angulares de la programación industrial. En C++, el desarrollador no solo tiene acceso al mecanismo clásico try/catch, sino también a todo un arsenal de técnicas avanzadas: desde RAII hasta garantías de seguridad de excepciones. En este artículo, analizaremos cómo escribir código C++ robusto que maneje fallos correctamente, no pierda recursos y se mantenga legible.
1. Fundamentos y trampas estándar de try/catch
La sintaxis básica es conocida por todos:
#include <iostream>#include <stdexcept>
void dangerousFunction(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // ... trabajo con value}
int main() { try { dangerousFunction(-5); } catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Generic error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown exception caught" << std::endl; } return 0;}Puntos importantes:
- Capture por referencia —
catch (const std::exception& e)evita el corte de objetos. - El orden de los bloques catch es importante: primero las excepciones más específicas, luego las generales.
- No abuse de
catch (...)— oculta todos los errores, incluidos los críticos (por ejemplo,std::bad_alloc). - Nunca lance excepciones desde destructores — esto provoca un comportamiento indefinido (generalmente una terminación abrupta).
2. RAII como alternativa a try/catch para la gestión de recursos
RAII (Resource Acquisition Is Initialization) es el patrón clave de C++ que hace que las excepciones sean seguras. La idea: el recurso se adquiere en el constructor y se libera en el destructor. Si ocurre una excepción, los destructores de todos los objetos creados se llaman automáticamente (desenrollado de pila).
#include <fstream>#include <vector>#include <stdexcept>
void processFile(const std::string& filename) { std::ifstream file(filename); // RAII: el archivo se abre en el constructor if (!file.is_open()) { throw std::runtime_error("Cannot open file: " + filename); }
std::vector<int> data; int value; while (file >> value) { data.push_back(value); } // El destructor de file cerrará el archivo automáticamente, // incluso si ocurre una excepción (por ejemplo, bad_alloc en push_back)}Ventajas de RAII sobre try/catch manual:
- No es necesario escribir bloques
finally— los destructores garantizan la limpieza. - El código se vuelve más lineal y legible.
- Las excepciones no provocan fugas de memoria, descriptores de archivos, etc.
3. Garantías de seguridad de excepciones
En C++, se distinguen tres niveles de garantía para las funciones:
- Garantía No-throw — la función nunca lanza excepciones. Se marca con
noexcept. - Garantía Fuerte — si ocurre una excepción, el estado del programa permanece sin cambios (operación atómica).
- Garantía Básica — ante una excepción, no hay fugas de recursos, los objetos permanecen en un estado consistente, pero su valor puede cambiar.
Ejemplo con garantía fuerte (idioma copy-and-swap):
#include <algorithm> // para std::swap#include <vector>#include <stdexcept>
class SafeVector { std::vector<int> data_;public: void push_back(int value) { // 1. Se crea