7. Manejo de excepciones y tratamiento de errores - ELAI-UPM

Anuncio
PROGRAMACIÓN C++ Y COMUNICACIONES.
206
7.
Manejo de excepciones y tratamiento de errores
Uno de los aspectos que con más dificultad se abordan en los distintos lenguajes de programación es el correcto tratamiento de los errores. Es decir, el funcionamiento normal de un algoritmo o programa contiene la dificultad inherente del problema que se quiere resolver, y como consecuencia generará un flujograma más o menos complicado. Sin embargo, todo ello se incrementa en complejidad de forma notable en el momento en el que se pretende verificar y comprobar los posibles errores o situaciones "no nominales" durante el proceso de ejecución. Este problema no es solo específico de la ingeniería del software, sino que a menudo es el mayor quebradero de cabeza en la mayoría de los proyectos de diseño y en particular aquellos en los que la seguridad es importante. De hecho, se puede decir sin problema, que es más complejo el sistema de redundancia y seguridad que se incluye en el software de control de un avión que el propio sistema de control. Al conjunto de situaciones "imprevistas" se la denomina "excepción", y al mecanismo dispuesto para reaccionar ante esas situaciones, se lo denomina como UPM-EUITI-2013. Miguel Hernando.
CAPÍTULO 7. MANEJO DE EXCEPCIONES Y TRATAMIENTO DE ERRORES
207
consecuencia "manejador de excepciones4". Existen muchos tipos de excepciones, pero para ubicar el tema, durante la ejecución de un programa puede ocurrir que un fichero no se pueda grabar (disco lleno), o que se agote la memoria, o que el usuario haya intentado realizar una operación no válida (por ejemplo desde el programa abrir un fichero sobre el que carece de permiso), etc. 7.1.
Tratamiento de excepciones en C++
El tratamiento de excepciones C++ sigue un proceso estructurado, por lo que a la hora de diseñarlo se diferencian tres conceptos básicos: 1.
Hay una parte de código en el que se considera que se pueden producir situaciones anómalas, y que consecuencia se pueden generar una serie de excepciones. Por eso, se considera que es posible que la acción que esa parte de código quiere realizar no se consiga, y por tanto es una zona de "intento de ejecución" (try). 2.
Cuando se detecta una circunstancia extraña o anómala (una excepción), se informa al proceso, función, o programa que ha ocurrido algo imprevisto, y se interrumpe el curso normal de ejecución. Para realizarlo se "lanza" una excepción (throw). Esa excepción es un objeto que contiene información sobre el error para que el que escuche sea capaz de reaccionar ante esta situación. 3.
Para que el programa pueda decidir que hacer en estos casos, tras intentar ejecutar algo susceptible de fallar, indicará una zona del código que se ejecutará para reaccionar ante esas sistuaciones. Para ello "captura" (catch) las excepciones lanzadas en la zona de "intento" y se decide que hacer al respecto con la información que estas contienen. En el fondo el mecanismo es un mecanismo de salto controlado. Al código encargado de reaccionar ante una determinada excepción se lo denomina "manejador" de la excepción. Durante la ejecución de un programa pueden existir dos tipos de circunstancias excepcionales: síncronas y asíncronas. Las primeras son las que ocurren dentro del programa. Por ejemplo, que se agote la memoria o cualquier otro tipo de error. Son a estas a las que nos hemos estado refiriendo. Las excepciones asíncronas son las que tienen su origen fuera del programa, a nivel del Sistema Operativo. Por ejemplo que se pulsen las teclas Ctrl+C. 4
Exception Handler
PROGRAMACIÓN C++ Y COMUNICACIONES.
208
Generalmente las implementaciones C++ solo consideran las excepciones síncronas, de forma que no se pueden capturar con ellas excepciones tales como la pulsación de una tecla. Dicho con otras palabras: solo pueden manejar las excepciones lanzadas con la sentencia throw. Siguen un modelo denominado de excepciones síncronas con terminación, lo que significa que una vez que se ha lanzado una excepción, el control no puede volver al punto que la lanzó. El "handler" no puede devolver el control al punto de origen del error mediante una sentencia return. En este contexto, un return en el bloque catch supone salir de la función que contiene dicho bloque. El sistema estándar C++ de manejo de excepciones no está diseñado para manejar directamente excepciones asíncronas, como las interrupciones de teclado, aunque pueden implementarse medidas para su control. Además las implementaciones más usuales disponen de recursos para menejar las excepciones del Sistema Operativo Antes de ver un ejemplo completo se verá en primer lugar como se ha previsto en C++ la transcripción de cada uno de estos compoenentes del mecanismo de tratamiento de excepciones. El bloque "try"
En síntesis podemos decir que el programa se prepara para cierta acción, decimos que "lo intenta". Para ello se especifica un bloque de código cuya ejecución se va a intentar ("try‐block") utilizando la palabra clave try: try
{
// conjunto de instrucciones en las que
// se puede producir una excepción y que quedan
// afectadas por la misma
...
}
Un ejemplo sencillo sería la operación de apertura y grabación de información en un fichero. Es posible que no se pueda abrir el fichero por no tener permisos de escritura en el luegar indicado por el usuario, y por tanto cualquier instrucción de escritura tras fracasar en la apertura debe ser evitada. La idea del bloque try en este caso podría ejemplarizarse de la siguiente forma: UPM-EUITI-2013. Miguel Hernando.
CAPÍTULO 7. MANEJO DE EXCEPCIONES Y TRATAMIENTO DE ERRORES
209
INTENTA { 1.‐ Abrir el fichero 2.‐ Guardar la información 3.‐ Cerrar el fichero } SI HAY UN ERROR CAPTURALO Y EJECUTA { 4.‐ Informa al usuario } Luego habitualmente el bloque de intento no sólo incluye la zona susceptible de generar errores, sino que también incluye la parte de código que debe evitar ser ejecutada en caso de que estos errores se produzcan. Así en el ejemplo, si la apertura de fichero da un error, habrá que evitar ejecutar las operaciones indicadas por 2 y por 3. Por ello, si 1 lanza una excepción de "ERROR DE APERTURA", se interrumpe la ejecuón secuencial del programa y se "SALTA" hasta el manejador (4) que informará al usuario de este error. Se informa por tanto al programa que si se produce un error durante el "intento", entonces se debe transferir el control de ejecución al punto donde exista un manejador de excepciones ("handler") que coincida con el tipo de excepción lanzado. Si no se produce ninguna excepción, el programa sigue su curso normal. De lo dicho se deduce inmediatamente que se pueden lanzar excepciones de varios tipos y que pueden existir también receptores (manejadores) de varios tipos; incluso manejadores "universales", capaces de hacerse cargo de cualquier tipo de excepción. Así pues, try es una sentencia que en cierta forma es capaz de especificar el flujo de ejecución del programa. Un bloque‐intento debe ser seguido inmediatamente por el bloque manejador de la excepción, el cual se especifica por medio de la palabra clave catch. El bloque "catch"
Mediante la palabra clave catch especificamos el código encargado de reaccionar ante una determinada excepción o conjunto de excepciones. Como ya se ha comentado, se dice que un manejador "handler" captura una excepción. En C++ es obligatorio que tras un bloque try se incluya al menos un bloque catch, por lo que la sintaxis anterior quedaría completa de la siguiente forma: try {
// bloque de código que se intenta
...
}
catch (...) { // bloque manejador de posibles excepciones
...
PROGRAMACIÓN C++ Y COMUNICACIONES.
210
}
...
// continua la ejecución normal
El "handler" es el sitio donde continua el programa en caso de que ocurra la circunstancia excepcional (generalmente un error) y donde se decide qué hacer. A este respecto, las estrategias pueden ser muy variadas (no es lo mismo el programa de control de un reactor nuclear que un humilde programa de contabilidad). En último extremo, en caso de errores absolutamente irrecuperables, la opción adoptada suele consistir en mostrar un mensaje explicando el error. Lanzamiento de una excepción " throw "
Las excepciónes no sólo las lanzan las funciones incluidas dentro de las librerías externas, sino que nosotros podemos querer informar que algo no ha ido bien dentro de la lógica de nuestro programa. Al igual que ocurre con el código de las librerías estandar, esto se realiza por medio de la instrucción throw. El lenguaje C++ especifica que todas las excepciones deben ser lanzadas desde el interior de un bloque‐intento y permite que sean de cualquier tipo. Como se ha apuntado antes, generalmente son un objeto (instancia de una clase) que contiene información. Este objeto es creado y lanzado en el punto de la sentencia throw y capturado donde está la sentencia catch. El tipo de información contenido en el objeto es justamente el que nos gustaría tener para saber que tipo de error se ha producido. En este sentido puede pensarse en las excepciones como en una especie de correos que transportan información desde el punto del error hasta el sitio donde esta información puede ser analizada. Todo esto lo veremos con más detalle a continuación. Como se ha comentado C++ permite lanzar excepciones de cualquier tipo. Sin embargo lo normal es que sean instancias de una clase tipo X, que contiene la información necesaria para conocer la naturaleza de la circunstancia excepcional (probablemente un error). El tipo X debe corresponder con el tipo de argumento usado en el bloque catch, de tal forma que por medio del tipo de las excepciones será posible ejecutar manejadores distintos aún siendo estas lanzadas desde elmismo bloque try. Nota: Se recomienda que las clases diseñadas para instanciar este tipo de objetos (denominadas Excepciones) sean específicas y diseñadas para este fin exclusivo, sin que tengan otro uso que la identificación y manejo de las excepciones. UPM-EUITI-2013. Miguel Hernando.
CAPÍTULO 7. MANEJO DE EXCEPCIONES Y TRATAMIENTO DE ERRORES
211
La expresión throw (X arg) inicializará un objeto temporal arg de tipo X, aunque puede que se generen otras copias por necesidad del compilador. En consecuencia puede ser útil definir un constructor‐copia cuando el objeto a lanzar pertenece a una clase que contiene subobjetos, como en el caso de cualquier otra clase cuyos objetos quieran ser pasados por valor (recuérdese el apartado dedicado al contructor de copia y el operador de asignación en el capítulo 3). En el siguiente ejemplo se pasa el objeto de tipo Out al manejador catch: #include <stdio.h>
bool pass;
class Out{};
// L.3: Para instanciar el objeto a lanzar
void festival(bool);
// L.4: prototipo
int main()
{
try {
// L.7: bloque intento
pass = true;
festival(true);
}
catch(Out o) { // L.11: la excepción es capturada aquí
pass = false; // L.12: si se produce una excepción
}
// L.13:
return pass ? (puts("Acierto!"),0) : (puts("Fallo!"),1);
}
void festival(bool firsttime){
if(firsttime) {Obj o; throw o; } // L.18: lanzar excepción
}
7.2.
Secuencia de ejecución del manejo de excepciones
Como puede verse, la filosofía C++ respecto al manejo de excepciones no consiste en corregir el error y volver al punto de partida. Por el contrario, cuando se genera una excepción el control sale del bloque try que lanzó la excepción (incluso de la función), y pasa al bloque catch cuyo manejador corresponde con la excepción lanzada (si es que existe). A su vez el bloque catch puede hacer varias cosas: •
Relanzar la misma excepción PROGRAMACIÓN C++ Y COMUNICACIONES.
212
•
Saltar a una etiqueta •
Terminar su ejecución normalmente (alcanzar la llave de cierre). Si el bloque catch termina normalmente sin lanzar una nueva excepción, el control se salta todos los bloques catch que hubiese asociados al bloque try correspondiente y sigue la ejecución del programa a continuación. Puede ocurrir que el bloque catch lance a su vez una excepción. Lo que nos conduce a excepciones anidadas. Esto puede ocurrir, por el hecho de que se ejecutan intrucciones y por tanto es posible que alguna de ellas vuelva a generar a su vez una excepción (supongase que es una excepción lanzada por intentar cerrar un fichero que no existe o que no se puede cerrar) Como se puede ver, un programador avanzado puede utilizar las excepciones C++ como un mecanismo de return o break multinivel, controlado no por una circunstancia excepcional, sino como un acto deliberado del programador para controlar el flujo de ejecución. se lanzan deliberadamente excepciones con la idea de moverse entre bloques a distintos niveles. Dado el nivel de este curso no se recomienda esta práctica hasta que no se esté totalmente familiarizado con la programación estructurada. Relanzar una excepcion
Si se ha lanzado previamente una excepción y se está en el bloque que la ha capturado, es posible repetir el lanzamiento de la excepción (el mismo objeto recibido en el bloque catch) utilizando simplemente la sentencia throw sin ningún especificador. No perder de vista que el lanzamiento throw, solo puede realizarse desde el interior de un bloque try, al que debe seguir su correspondiente "handler". El siguiente ejemplo sería incorrecto, dado que se intenta relanzar una excepción desde fuera de un bloque try: try {
...
if (x) throw A();
}
catch (A a) {
...
throw;
}
// lanzar excepción
// capturar excepción
// hacer algo respecto al error
// Error!! no está en un bloque try
UPM-EUITI-2013. Miguel Hernando.
CAPÍTULO 7. MANEJO DE EXCEPCIONES Y TRATAMIENTO DE ERRORES
213
El modo más habitual de realizar un relanzamiento es debido a que se trata de un bloque try anidado en otro bloque try (en la misma función o en funciones jerarquicamente por encima en la pila de llamadas). El siguiente ejemplo refleja este modo de proceder: void foo();
void main () {
try {
...
foo();
}
catch (A a) {
...
}
return 0;
}
void foo() {
...
if (x) try {
throw A();
}
catch (A) {
...
throw;
}
}
Descargar