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 [1] 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 ***
[1] 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.