Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


1.6.3  Excepciones imprevistas

§1  Sinopsis

Es innecesario decir que las excepciones también pueden provocar excepciones; desde luego, pueden provocar múltiples errores, pero en general son de alguno de los cinco tipos que se relacionan:

  • 1.-  No existe manejador para la excepción ("No handler for the exception").

  • 2.-  Lanzada una excepción no prevista ("Unexpected exception thrown")

  • 3.-  Una excepción solo puede ser lanzada de nuevo desde un manejador ("An exception can only be re-thrown in a handler")

  • 4.-  Durante la limpieza de la pila un destructor debe manejar su propia excepción ("During stack unwinding, a destructor must handle its own exception").

  • 5.-  Memoria agotada ("Out of memory").

En este capítulo presentamos los comportamientos adoptados por el compilador para los dos primeros casos; generalmente debidos a que no hemos diseñado correctamente el sistema de manejo excepciones de nuestro programa. Algo así como el sistema de "protección contra fallos del sistema de emergencia".

Las dos primeras situaciones erróneas son independientes.  Las causas/medidas-a-adoptar responde al siguiente esquema:

  • La primera es el caso que se lance una excepción para la que no se ha previsto un manejador adecuado; las denominamos excepciones sin manejador .  En esencia el sistema consiste en que puede instalarse un manejador genérico (manejador de terminación) que se haga cargo de la situación si hemos olvidado instalar el "handler" adecuado para una excepción concreta. Incluso veremos que si hemos olvidado instalar este manejador universal, el compilador proporciona uno por defecto.

  • La segunda situación contempla que una función lance una excepción que no está incluida en su especificador de excepción. Este concepto se explica más adelante (1.6.4),  por ahora adelantemos que C++ permite especificar de antemano que excepciones (tipos de objetos) podrán ser lanzados desde una función determinada (recordemos que en C++ todo ocurre dentro de funciones). A estas situaciones las denominamos excepciones imprevistas .  Veremos que el patrón de actuación es parecido al anterior; es posible definir para estos "imprevistos" un comportamiento (función) que se encargue de la situación. En caso que no hayamos establecido nada concreto, el compilador proporciona un protocolo de actuación por defecto.

§2  Excepciones sin manejador

Recordemos ( 1.6.1) que si durante la ejecución de un bloque try se lanza una excepción y no se encuentra ningún manejador adecuado se adoptan las siguientes medidas:

Se invoca la función terminate()

a:  Se ha establecido una función t_func por defecto con set_terminate().

terminate invoca t_func (que debe terminar el programa).

b:  No se ha establecido ninguna función por defecto con set_terminate()

terminate invoca la función abort().

El siguiente ejemplo muestra lo que ocurre cuando el programa encuentra una excepción no soportada.

#include <except.h>
#include <process.h>
#include <stdio.h>
bool pass;
class Out{};
void final();              // prototipo
void festival(bool);       // ídem.
void test();               // ídem.


int main() {               // =============
   set_terminate(final);   // M.1: añade final a la lista
   test();                 // M.2: test lanza la excepción Out sin manejador
   return pass ? (puts("Salir del test"),0) :

                 (puts("Seguir el test"),1);
}

void final(){

  puts("*** Nadie captura la excepción ***");

  abort();

}

void festival(bool firsttime){ if(firsttime) throw Out(); }
void test() {festival(true); }

Salida:

*** Nadie captura la excepción ***

Comentario:

La sentencia M.1 registra la función final como manejador por defecto , de forma que a partir de este momento si se lanza una excepción que no encuentra manejador adecuado,  se invocará esta función.

M.2 invoca a test que invoca a su vez a festival con true como argumento. Lo que hace que esta última lance una excepción con una instancia de Out.

En el programa no existe ningún manejador específico previsto para esta excepción (ni para ninguna otra). En realidad no se ha previsto ningún dispositivo para manejar excepciones, no existe ningún bloque try, por lo que es invocada la función terminate, que invoca a su vez a la función final que se había instalado al principio. Esta última es la responsable de la salida obtenida y de terminar el programa.

§2.1  terminate

Esta función de la Librería Estándar (except.h), es invocada cuando se lanza una excepción que no encuentra el manejador adecuado.

Sintaxis:

void terminate();

Descripción:

La misión de esta función es simplemente verificar si existe alguna función de usuario definida como reserva para el caso de no encontrarse un manejador adecuado para la excepción lanzada (esta función de reserva se denomina manejador de terminación y se instala como se indica a continuación). Si la función existe, terminate la invoca; si no existe, termnate realiza una llamada a abort ( 1.5.1), lo que origina la terminación inmediata del programa.

En otras palabras: es posible modificar la forma en que termina el programa cuando se genera una excepción que no tiene un "handler" adecuado. Si se desea terminar con algo distinto que la llamada a abort, se puede definir cualquier otra. Esta función, manejador de terminación, será llamada por la función terminate si ha sido registrada mediante set_terminate .

§2.2  set_terminate

set_terminate es una función de Librería Estándar  <except.h>, que permite instalar una función que determina el comportamiento del programa cuando se lanza una excepción que no encuentra ningún "handler" específico. Podríamos decir que instala un manejador por defecto (el manejador de terminación).

Sintaxis:

typedef void (*terminate_handler)();
terminate_handler set_terminate(terminate_handler t_func);

Ejemplo:

set_terminate(final);
...

void final(){

  puts("*** Nadie captura la excepción ***");

  abort();

}

Descripción

Vemos que set_terminate es una función que devuelve un objeto terminate_handler y recibe un argumento del mismo tipo.  A su vez, terminate_hadler es un puntero a función que no recibe argumentos y devuelve void.

La acción a ejecutar está definida por t_func, este argumento debe ser el nombre de la función que queremos invocar en caso de que una excepción no encuentre un manejador adecuado.

  Evidentemente t_func debe responder a las expectativas, es decir:  Ser una función que no reciba argumentos y devuelva void. Debe ser definida de forma que termine el programa. Cualquier intento de volver a su invocadora, la función terminate, conduce a un comportamiento indefinido del programa. Tampoco se puede lanzar una excepción desde t_func.

Si no se ha previsto ningún manejador, entonces el programa llama a la función terminate, que a su vez termina con una llamada a la función abort ( 1.5.1), y el programa termina con el mensaje: Abnormal program termination. Si se desea que se llame cualquier otra función distinta de abort desde terminate entonces debemos instalar nuestra propia t_func e instalarla con set_terminate, lo que nos permitiría implementar cualquier acción que deseemos que no sea cubierta por abort.

§3  Excepciones imprevistas

Si una función lanza una excepción que no está incluida en su especificador de excepción (1.6.4), se produce una llamada a la función unexpected , que a su vez invoca a cualquier función establecida por set_unexpected .  Caso de no haberse establecido ninguna función, unexpected llama a terminate .

§3.1  unexpected

Esta función de la Librería Estándar <except.h> es invocada cuando una función lanza una excepción que no está incluida en su especificador de excepción (1.6.4).

Sintaxis:

void unexpected();

Descripción:

A su vez unexpected invoca a cualquier función establecida por set_unexpected .  Si no existe ninguna función registrada, unexpected llama a la función terminate .

Como puede verse en su definición, unexpected no devuelve nada, aunque a su vez puede lanzar una excepción. Ver ejemplo ( 1.6.4)

§3.2   set_unexpected

Función de Librería Estándar <except.h>.

Sintaxis

typedef void ( * unexpected_handler )();
unexpected_handler set_unexpected(unexpected_handler unexp_func);

  unexp_func define la función que se pretende instalar.

Descripción

Esta función permite instalar una función que será ejecutada en caso que una función invoque una excepción que no esté incluida en su especificador de excepción ( 1.6.4).

Como puede verse, el argumento a utilizar es un objeto tipo unexpected_handler, es decir: un puntero a función que no recibe argumentos y devuelve void.  En la práctica esto significa que se puede utilizar directamente el nombre de la función que se desea instalar, y que esta función debe ser del tipo adecuado (una función que no reciba argumentos y no devuelva nada).

La función instalada debe ser tal que termine el programa. No debe intentar volver a su invocadora (unexpected), ya que un intento de esta índole produciría un resultado indefinido. Por contra, unexp_func puede llamar a las funciones abort ( 1.5.1), exit ( 1.5.1) o terminate ().

§4  Corolario

El sistema C++ de tratamiento de errores ofrece infinitas combinaciones posibles. Cada circunstancia requiere una estrategia distinta, pero siempre deberíamos instalar un sistema, aunque fuese mínimo y rudimentario, para el tratamiento de excepciones. Es mucho más elegante salir del programa de forma controlada con un mensaje adecuado, y quizás escribiendo un fichero con el estatus y tipo de error, que terminar con un mensaje del Sistema.

Como hemos visto, el compilador establece por defecto un sistema que obedece al siguiente esquema: cuando una función lanza una excepción que no está incluida en su especificador de excepción se lanza unexpected(). Si no se ha previsto otra cosa unexpected() invoca a terminate(). A su vez la acción por defecto de terminate() es invocar a abort().

Generalmente los programas tienen una vida larga y sujeta a cambios; revisiones sucesivas que los van mejorando. No existe inconveniente para que el sistema de control se vaya afinando y sofisticando a partir de un diseño inicial más o menos rudimentario, en función de la experiencia obtenida con su explotación.

Como punto de partida podría servir el siguiente esquema:

#include <signal.h>

#include <except.h>

 

...

void noHandler();           // excepciones sin manejador

void imprevistas();         // excepciones imprevistas

 

int main() {                // =================

  set_terminate(noHandler);      // añade noHandler

  set_unexpected(imprevistas);   // añade imprevistas
  ...                       // nuestro proceso...

  return 0;                 // Ok. el programa concluye correctamente

}

 

void noHandler() {          // definición

  cerr << "Excepción sin manejador. Programa terminado";

  raise(SIGABRT);    // El programa termina con error

}

 

void imprevistas() {        // definición

  cerr << "Excepción imprevista. Programa terminado";

  abort();           // El programa termina con error

}