El post que buscas se encuentra eliminado, pero este también te puede interesar

C++ (VigesimoSegunda parte)

Anuncios

C++ (VigesimoSegunda parte)


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
}

1.6.4 Especificación de excepciones
§1 Sinopsis
En C++ existe una opción denominada especificación de excepción que permite señalar que tipo de excepciones puede lanzar una función directa o indirectamente (en funciones invocadas desde ella). Este especificador se utiliza en forma de sufijo en la declaración de la función y tiene la siguiente sintaxis:
throw (<lista-de-tipos> // lista-de-tipos es opcional
La ausencia de especificador indica que la función puede lanzar cualquier excepción.
El mecanismo de excepciones fue introducido en el lenguaje en 1989, pero la primitiva versión adolecía de un problema que podemos resumir como sigue: Supongamos que tenemos una función de librería cuya definición, contenida en un fichero de cabecera, es del tipo:
void somefuncion (int);
Lo normal es que las "tripas" de la función queden ocultas al usuario, que solo dispone de la información proporcionada por el prototipo ( 4.4.1), pero es evidente que en estas circunstancias es imposible saber si la función puede lanzar una excepción y en consecuencia, decidir si de deben tomar (o no) las medidas apropiadas para su captura.
Años después, y ante la confusión creada, el Comité de Estandarización decidió incluir la especificación de excepciones que comentamos en este capítulo. Como puede verse es un modo de incluir en el prototipo información suficiente para que el usuario conozca que tipo de excepciones pueden esperarse de una función (si es que las hay).

§2 Ejemplos de funciones con especificadores de excepción:
void f1(); // f1 puede lanzar cualquier excepción
void f2() throw(); // f2 no puede lanzar excepciones
void f3() throw(BETA); // f3 solo puede lanzar objetos BETA
void f4() throw(A, B*); /* f4 puede lanzar excepciones derivadas públicamente de A o un puntero a derivada públicamente de B */
Nota: La sintaxis utilizada con f2 es la forma estándar C++ para especificar que una función no puede lanzar excepciones, y que salvo indicación en contrario (§4 ), tampoco las funciones que puedan ser invocadas desde ella. No obstante, los compiladores Borland C++ y MS Visual C++ disponen de otra posibilidad sintáctica para el mismo propósito ( 4.4.1b).

§3 Tenga en cuenta que las funciones con especificador de excepción no son susceptibles de sustitución inline ( 4.4.6b). Por ejemplo, la sentencia:
inline void f1() throw(int) { ... }
daría lugar a una advertencia del compilador: Warning: Functions with exception specifications are not expanded inline
§4 Las excepciones señaladas para una función no afectan a otras funciones que pudieran ser llamadas durante su ejecución. Por ejemplo:
func1() throw() { // func1 no puede lanzar excepciones
... // en esta zona no se lanzarán excepciones
func2();
}
func2() throw(A); // func2 puede lanzar un objeto A
...
try {
...
func1
}

Durante la ejecución del bloque de código de func1 no pueden lanzarse excepciones de ningún tipo, pero si ocurren circunstancias adecuadas mientras se está ejecutando la invocación a func2, desde esta sí pueden lanzarse objetos tipo A.
Todos los prototipos y definiciones de estas funciones deben tener un especificador de excepción conteniendo la misma <lista-de-tipos>. Si una función lanza una excepción cuyo tipo no está incluido en su especificación, el programa llama a la función unexpected ( 1.6.3Excepciones imprevistas).

§5 El sufijo no es parte del tipo de la función; en consecuencia, un puntero a función no se verá afectado por el especificador de excepción que pueda tener la función. Este tipo de punteros solo comprueba el tipo de valor devuelto y los argumentos ( 4.2.4a). Por consiguiente, lo siguiente es legal:
void f2(void) throw();
void f3(void) throw(BETA);
void (* fptr)(); // Puntero a función devolviendo void
fptr = f2; // fptr se puede asignar a cualquiera
fptr = f3; // de las funciones f2 y f3

§6 Hay que prestar atención cuando se sobrecontrolan funciones virtuales, porque la especificación de excepción no se considera parte del tipo de función y existe el riesgo de violaciones en el diseño del programa.
§7 Ejemplo 1
En el siguiente ejemplo la definición de la clase derivada BETA::vfunc se hace de forma que no puede lanzar ninguna excepción; se trata de una variación de la definición original en la clase base.
class ALPHA {
public:
struct ALPHA_ERR {};
virtual void vfunc(void) throw (ALPHA_ERR) {} // Especificador de excepción
};
class BETA : public ALPHA {
void vfunc(void) throw() {} // Se cambia el especificador de excepción
};
§8 Ejemplo 3
Este ejemplo especifica que excepciones pueden lanzar las funciones festival y test. Ninguna otra excepción podrá ser lanzadas desde ambas.
#include <stdio.h>
bool pass;
class Out{};
// festival solo puede lanzar excepciones Out
void festival(bool firsttime) throw(Out) {
if(firsttime) throw Out();
}
void test() throw() { // test no puede lanzar ninguna excepción
try { festival(true); }
catch(Out& e){ pass = true; }
}
int main() {
pass = false;
test();
return pass ? (puts("Excepción manejada por test",0) :
(puts("Sin excepción!!" ,1);
}
Salida:
Excepción manejada por test
Si festival generase una excepción distinta de Out, se consideraría una excepción imprevista, y el control del programa sería transferido a la función prevista para estos casos (ver al respecto el ejemplo siguiente).
§9 Ejemplo 4
Se muestra que test no puede lanzar ninguna excepción. Si alguna función (por ejemplo el operador new) en el cuerpo de test lanza una excepción, la excepción debe ser capturada y manejada dentro del propio cuerpo de test. En caso contrario, la excepción representaría una violación de la especificación de no-excepciones establecida para dicha función. Es posible establecer que set_unexpected() acepte un manejador diferente, pero en cualquier caso, será invocada la función que se haya previsto para estos casos.
#include <except.h>
#include <process.h>
#include <stdio.h>
bool pass;
class Out{};
void imprevisto(){ puts("*** Fallo ***"; exit(1); }
void festival(bool firsttime) throw(Out) { // festival solo puede lanzar
if(firsttime) throw Out(); // excepciones Out
}
void test() throw() { // test no puede lanzar ninguna excepción
try { festival(true); }
catch(Out& e){ pass = true; throw; } // Error: intenta ralanzar Out
}
int main() { // ============
set_unexpected(imprevisto);
pass = false;
test();
return pass ? (puts("Excepción manejada por test",0) :
(puts("Sin excepción !!" ,1);
}
Salida:
*** Fallo ***
Inicio.
________________________________________
Si en C++Builder coexisten simultanea e independientemente, un prototipo y una definición de la función, la especificación de excepción debe incluirse en ambos, de lo contrario se produciría un error de compilación.

Anuncios

0 comentarios - C++ (VigesimoSegunda parte)