MANEJO DE EXCEPCIONES en C++ Dr. Oldemar Rodríguez R. Escuela de Informática Universidad Nacional C++ posee un mecanismo de gestión de errores incorporado que se denomina manejo de excepciones. La utilización del manejo de excepciones permite gestionar y responder a los errores en tiempo de ejecución. El manejo de excepciones de C++ está construido a partir de tres palabras claves: try, catch y throw. En términos generales, las sentencias de un programa que se quiere monitorear, para vigilar las excepciones, están contenidas en un bloque try. Si se produce una excepción (un error) en un bloque try, éste se eleva (utilizando throw) y usando catch se captura la excepción y se procesa. La forma general de try y catch es: try { // bloque try throw excepción; } catch (type1 arg) { // bloque catch } catch (type2 arg) { // bloque catch } catch (type3 arg) { // bloque catch } ……………. catch (typeN arg) { // bloque catch } El bloque try debe contener la porción de programa que se quiere monitorear. Esto puede ser tan corto como unas pocas sentencias dentro de una función o tan grande como el código de la función main() (que da lugar a que se monitorear todo el programa). Cuando se genera una excepción, la sentencia catch correspondiente la captura y la procesa. Puede haber más de una sentencia catch asociada a un bloque try. La sentencia catch utilizada depende del tipo de excepción. Esto es, si el tipo de datos especificado por catch coincide con el de la excepción, se ejecuta esa sentencia catch. (Las otras sentencias no se ejecutan). Cuando se captura una excepción arg recibe un valor. Puede capturar cualquier tipo de datos, incluidas las clases creadas por el programador. La forma general de la sentencia throw es la siguiente: throw exception; throw debe ejecutarse dentro del bloque try, que es lo más adecuado, o desde cualquier función que la llame (directa o indirectamente), exception es el valor generado. Nota: Si se produce una excepción para la que no existe una sentencia catch aplicable, el programa puede terminar de forma anormal. Si su compilador admite el estándar ANSI C++ propuesto, la generación de una excepción no gestionada provoca una llamada a la función terminate(). Por omisión, terminate() llama a abort() para que su programa se detenga, pero, si lo desea, puede especificar su propio gestor de finalización. EJEMPLOS 1. Este sencillo ejemplo muestra la forma de funcionamiento de el manejo de excepciones en C++: // Un ejemplo sencillo de manejo de excepciones. #include <iostream> using std::endl; using std::cin; using std::cout; int main() { cout << "inicio \n"; try { // inicio de un bloque try cout << "Dentro del bloque try \n"; throw 10; // generación de un error cout << "Esto no se ejecutará correctamente"; } catch (int i) { // captura de un error cout << "¡Capturado un error! Su número es: "; cout << i << "\n"; } cout << "fin"; return 0; } Este programa da lugar a la siguiente salida: inicio Dentro del bloque try ¡Capturado un error! Su número es: 10 fin Observe este programa cuidadosamente. En él hay un bloque try que contiene tres sentencias y una sentencia catch(int i) que procesa una excepción entera. Dentro del bloque try, sólo se ejecutarán dos de estas sentencias: la primera sentencia cout y la sentencia throw. Una vez lanzada una excepción, el control pasa a la expresión catch y el bloque try termina. Esto es, no se llama a catch. En lugar de ello, se le transfiere la ejecución del programa. (La pila se inicializa de nuevo para llevar esto a cabo.) De este modo, la sentencia cout, que sigue throw, no se ejecutará. Cuando se ejecuta la sentencia catch el programa continúa con las sentencias que siguen a catch. Sin embargo, un bloque catch finalizará normalmente con una llamada a exit(), abort(), etc., porque el manejo de excepciones se utiliza a menudo para gestionar errores fatales. 2. Como ya se ha indicado, el tipo de una excepción debe coincidir con el tipo especificado en una sentencia catch. En el ejemplo anterior, si se cambia a double el tipo de la sentencia catch, no podrá capturarse la excepción y el programa terminará de un modo anormal. Este programa refleja este cambio: // Este ejemplo no va a funcionar. #include <iostream> using std::endl; using std::cin; using std::cout; int main( ) { cout << "inicio\n"; try { // inicio de un bloque try cout << "Dentro del bloque try\n"; throw 10; // generación de un error cout << "Esto no se ejecutará correctamente"; } catch (double i) { // No funcionará para una excepción entera cout << "¡Capturado uno! Su número es: "; cout << i << "\n"; } cout << "fin"; return 0; } Este programa da lugar a la siguiente salida, ya que la sentencia double catch no capturará la excepción entera: inicio Dentro del bloque try Detención anormal del programa 3. Se puede generar una excepción desde una sentencia que no está dentro del bloque try siempre que esté incluida en una función que esté, a su vez, dentro del bloque try. Este programa, por ejemplo, es correcto: #include <iostream> using std::endl; using std::cin; using std::cout; void Xtest(int test) { cout << "Dentro de Xtest, test vale: " << test << "\n"; if(test!=0) throw test; } int main() { cout << "inicio\n"; try { // inicio de un bloque try cout << "Dentro del bloque try\n"; Xtest(0); Xtest(1); Xtest(2); } catch (int i) { // captura de un error cout << "Capturado uno! Su número es: "; cout << i << "\n"; } cout << "fin\n"; system("pause"); system("cls"); return 0; } • El programa anterior produce la salida: inicio Dentro del bloque try Dentro de Xtest, test vale: 0 Dentro de Xtest, test vale: 1 ¡Capturado uno! Su número es: 1 fin 4. Un bloque try puede encontrarse dentro de una función. Cuando se da este caso, cada vez que se introduce la función, se redefine el manejo de excepciones con respecto a esa función. #include <iostream> using std::endl; using std::cin; using std::cout; void Xhandler(int test) { try { if(test!=0) throw test; } catch(int i) { cout << "Capturado uno! Excep#: " << i << "\n"; } } int main() { cout << "inicio\n"; Xhandler(1); Xhandler(2); Xhandler(0); Xhandler(3); cout << "fin\n"; system("pause"); system("cls"); return 0; } El programa anterior produce la salida: inicio ¡Capturado uno! Excep#: 1 ¡Capturado uno! Excep#: 2 ¡Capturado uno! Excep#: 3 fin 5. Como ya se comentó anteriormente, puede haber más de una sentencia catch asociada con un bloque try. De hecho, lo normal es que sea así. Sin embargo, cada sentencia catch debe capturar un tipo diferente de excepción. Por ejemplo, el siguiente programa captura enteros y cadenas: #include <iostream> using std::endl; using std::cin; using std::cout; void Xhandler(int test) { try { if(test!=0) throw test; else throw "El valor es cero"; } catch(int i) { cout << "Capturado uno! Ex.#: " << i << '\n'; } catch(char *str) { cout << "Capturada una cadena: "; cout << str << '\n'; } } int main() { cout << "inicio\n"; Xhandler(1); Xhandler(2); Xhandler(0); Xhandler(3); cout << "fin\n"; system("pause"); system("cls"); return 0; } Este programa produce la siguiente salida: inicio ¡Capturado uno! ExA: 1 ¡Capturado uno! ExA: 2 Capturada una cadena: El valor es cero ¡Capturado uno! Ex.#: 3 fin Como se observa, cada sentencia catch responde sólo a su propio tipo. En general, las expresiones catch se comprueban de acuerdo a su orden de aparición dentro del programa. Sólo se ejecuta la sentencia que coincida. El resto de bloques catch se ignoran. Existen diversos detalles y matices del manejo de excepciones en C++ que lo hacen fácil de utilizar. En algunas circunstancias se necesitará un gestor de excepciones que capture todas ellas en lugar de capturar una de un determinado tipo. Esto es sencillo de llevar a cabo. Basta con utilizar simplemente esta forma de catch: catch(...) { // procesamiento de todas las excepciones } El siguiente programa presenta catch(...) #include <iostream> using std::endl; using std::cin; using std::cout; void Xhandler(int test) { try { if(test==0) throw test; // genera un entero if(test==1) throw 'a'; // genera un carácter if(test==2) throw 123.23; // genera un doble } catch(...) { // captura todas las execpciones cout << "Capturada una!\n"; } } int main () { cout << "inicio\n"; Xhandler(0); Xhandler(1); Xhandler(2); cout << "fin\n"; system("pause"); system("cls"); return 0; } Este programa muestra la siguiente salida: inicio ¡Capturada una! ¡Capturada una! ¡Capturada una! fin Los tres throw han sido capturados utilizando una única sentencia catch. Un buen uso para catch(...) es el de última sentencia de una agrupación de capturas. Por ejemplo, la siguiente versión, ligeramente diferente del programa anterior, captura explícitamente excepciones enteras dejando que catch(...) capture todas las demás: #include <iostream> using std::endl; using std::cin; using std::cout; void Xhandler(int test) { try { if(test==0) throw test; // genera un entero if(test==1) throw 'a'; // genera un carácter if(test==2) throw 123.23; // genera un doble } catch(int i) { // captura todas las execpciones cout << "Capturada " << i << '\n'; } catch(...) { // captura todas las execpciones cout << "Capturada una!\n"; } } int main () { cout << "inicio\n"; Xhandler(0); Xhandler(1); Xhandler(2); cout << "fin\n"; system("pause"); system("cls"); return 0; } La salida producida por este programa es: inicio Capturada 0 ¡Capturada una! ¡Capturada una! fin Como sugiere el ejemplo, el uso de catch(...) por omisión es una buena forma de capturar todas las excepciones que no desee gestionar explícitamente. La captura de todas las excepciones impide que una excepción incontrolada provoque una terminación anormal de un programa. Otros ejemplos: División entre 0 ver Ej8 Excepción en el operador New ver Ej9