Tabla de contenidos
Mejorar la recuperación de errores es una de las maneras más potentes de incrementar la robustez de su código.
Una de las principales características de C++ es el tratamiento o manejo de excepciones, el cual es una manera mejor de pensar acerca de los errores y su tratamiento. Con el tratamiento de excepciones:
1. El código de manejo de errores no resulta tan tedioso de escribir y no se entremezcla con su código «normal». Usted escribe el código que desea que se ejecute, y más tarde, en una sección aparte, el código que se encarga de los problemas. Si realiza varias llamadas a la misma función, el manejo de errores de esa función se hará una sola vez, en un solo lugar.
2. Los errores no pueden ser ignorados. Si una función necesita enviar un mensaje de error al invocador de esa función, ésta «lanza» un objeto que representa a ese error fuera de la función. Si el invocador no «captura» el error y lo trata, éste pasa al siguiente ámbito abarcador, y así hasta que el error es capturado o el programa termina al no existir un manejador adecuado para ese tipo de excepción.
En la mayoría de ejemplos de estos volúmenes, usamos la función assert() para lo que fue concebida: para la depuración durante el desarrollo insertando código que puede deshabilitarse con #define NDEBUG en un producto comercial. Para la comprobación de errores en tiempo de ejecución se utilizan las funciones de require.h (assure( ) y require( )) desarrolladas en el capítulo 9 del Volumen 1 y repetidas aquí en el Apéndice B. Estas funciones son un modo conveniente de decir, «Hay un problema aquí que probablemente quiera manejar con un código algo más sofisticado, pero no es necesario que se distraiga con eso en este ejemplo.» Las funciones de require.h pueden parecer suficientes para programas pequeños, pero para productos complicados deseará escribir un código de manejo de errores más sofisticado.
El tratamiento de errores es bastante sencillo cuando uno sabe exactamente qué hacer, puesto que se tiene toda la información necesaria en ese contexto. Simplemente se trata el error en ese punto.
El problema ocurre cuando no se tiene suficiente información en ese contexto, y se necesita pasar la información sobre el error a un contexto diferente donde esa información sí que existe. En C, esta situación puede tratarse usando tres enfoques:
2. Usar el poco conocido sistema de manejo de señales de la biblioteca estándar de C, implementado en las funciones signal( ) (para determinar lo que ocurre cuando se presenta un evento) y raise( ) (para generar un evento). De nuevo, esta alternativa supone un alto acoplamiento debido a que requiere que el usuario de cualquier biblioteca que genere señales entienda e instale el mecanismo de manejo de señales adecuado. En proyectos grandes los números de las señales de las diferentes bibliotecas puede llegar a entrar en conflicto.
Cuando se consideran los esquemas de tratamiento de errores para C++, hay un problema adicional que es crítico: Las técnicas de C de señales y setjmp( )/longjmp( ) no llaman a los destructores, por lo que los objetos no se limpian adecuadamente. (De hecho, si longjmp( ) salta más allá del final de un ámbito donde los destructores deben ser llamados, el comportamiento del programa es indefinido.) Esto hace casi imposible recuperarse efectivamente de una condición excepcional, puesto que siempre se están dejando objetos detrás sin limpiar y a los que ya no se tiene acceso. El siguiente ejemplo lo demuestra con setjmp/longjmp:
//: C01:Nonlocal.cpp // setjmp() & longjmp(). #include <iostream> #include <csetjmp> using namespace std; class Rainbow { public: Rainbow() { cout << "Rainbow()" << endl; } ~Rainbow() { cout << "~Rainbow()" << endl; } }; jmp_buf kansas; void oz() { Rainbow rb; for(int i = 0; i < 3; i++) cout << "there's no place like home" << endl; longjmp(kansas, 47); } int main() { if(setjmp(kansas) == 0) { cout << "tornado, witch, munchkins..." << endl; oz(); } else { cout << "Auntie Em! " << "I had the strangest dream..." << endl; } } ///:~
Listado 2.1. C01/Nonlocal.cpp
El problema con C++ es que longjmp( ) no respeta los objetos; en particular no llama a los destructores cuando salta fuera de un ámbito.[1] Puesto que las llamadas a los destructores son esenciales, esta propuesta no es válida para C++. De hecho, el estándar de C++ aclara que saltar a un ámbito con goto (pasando por alto las llamadas a los constructores), o saltar fuera de un ámbito con longjmp( ) donde un objeto en la pila posee un destructor, constituye un comportamiento indefinido.