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.1  Lanzar una excepción

Nota:  La comprensión de los detalles contenidos en este epígrafe y siguientes, relativos al mecanismo de excepciones C++, requiere un conocimiento previo de las clases ( 4.11), por lo que aconsejamos al estudiante que realiza una primera lectura, seguir ahora con otros tópicos y volver cuando tenga conocimiento de ellas.

§1  Sinopsis

El bloque en que queramos controlar una circunstancia excepcional (error) se señala al compilador precediéndolo de la palabra clave try.  Lo denominamos bloque-intento ("Try block"), y como cualquier otro bloque estar delimitado por un corchetes { } ( 3.2.6):

try {    // bloque-intento

  ...

}

Durante la ejecución de este bloque, el proceso sigue los siguientes pasos:

a:  Se produce una circunstancia excepcional:

Se lanza la excepción señalada por la sentencia throw

El programa busca por un manejador ("handler") adecuado a la excepción

a1  Se encuentra el "handler"

La pila (stack) es recorrida hacia abajo hasta el punto donde está el manejador.

El control del programa es transferido al manejador.

a2  No se encuentra ningún manejador  (1.6.3 Excepciones imprevistas).

Se invoca la función terminate()

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

terminate invoca t_func (que debe terminar el programa).

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

terminate invoca la función abort().

b:  No se produce ninguna excepción:

El programa sigue su ejecución normal (saltándose los bloques catch que pudieran seguir).

§2  Se lanza una excepción

Hemos señalado ( 1.6) que, salvo los casos en que son lanzadas por las propias librerías C++ (como consecuencia de algún error), las excepciones no se lanzan espontáneamente.  Es el programador el que debe incluir una sentencia (generalmente condicional) para lanzar la excepción en su caso.

Las excepciones se lanzan mediante una sentencia throw que obligatoriamente debe estar situada en el interior de un bloque try (o en un bloque o función anidada en él).  También obligatoriamente debe seguir, al menos un bloque catch ( 1.6.2).

Sintaxis:

throw expresión-de-asignación;

Ejemplo (las dos sentencias son equivalentes):

throw Obj();

throw (Obj o);

El lenguaje C++ permite que lanzar excepciones de cualquier tipo.  Pero generalmente son instancias de una clase tipoX, que contiene la información necesaria para conocer la naturaleza de la circunstancia excepcional (probablemente un error).  El tipoX debe corresponder con el tipo de argumento usado en el bloque catch.

Nota:  Se recomienda que las clases diseñadas para instanciar este tipo de objetos sean específicas y diseñadas para este fin exclusivo, sin que tengan otro uso que la identificación y manejo de las excepciones.


La expresión throw (X arg) inicializa 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 ( 4.11.2d4) cuando el objeto a lanzar pertenece a una clase que contiene subobjetos.

Ejemplo 1:

En el siguiente ejemplo se pasa el objeto Out al manejador catch de la línea 11.

#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: manejador (la excepción es capturada aquí)

     pass = false;      // L.12: valor de pass si se produce una excepción

   }                    // L.13:
   return pass ? (puts("Acierto!"),0) : (puts("Fallo!"),1);  // L.14:
}

 

void festival(bool firsttime){       // L.17: definición de festival

   if(firsttime) {Obj o; throw o; }  // L.18: lanzar excepción

}

Salida:

Fallo!

Comentario:

El bloque-intento try que comienza en L.7 y el manejador catch que comienza en L.11, forman el mecanismo de excepción junto con la clase Out que sirve para instanciar el objeto que se lanzará si se produce una excepción.  Cualquier excepción lanzada dentro del bloque try será capturada en la línea 11 (si es del tipo adecuado).

Observese que catch y su bloque (L11-13) son en realidad como una función que devolviera void y que debe recibir un tipo Out.  El control será transferido a L.11 si la excepción es del este tipo, lo que efectivamente sucede, ya que la sentencia de L.18 indica que la excepción lanzada o, es un objeto tipo Out.  Después de ejecutado el bloque catch el control del programa sigue en L.14.

Observe que la sentencia L.18 podría haberse sustituido por:

if(firsttime) throw Out();    // L.18a: Lanzar excepción.

más compacta pero equivalente, ya que Out(), es una invocación al constructor por defecto de la clase (4.11.2d1), que produce un objeto exactamente igual al objeto o producido en la sentencia Obj o. [1]

Observe también que en este caso no se trata propiamente de un error, puesto que la excepción es lanzada inevitablemente.  Aquí el mecanismo de excepción se utiliza más bien como un sistema de return multinivel.

§3  Relanzar una excepción

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".

Ejemplo  (ver también un ejemplo ejecutable ):

try {

  ...

  if (x) throw A();  // lanzar excepción

}

catch (A a) {        // capturar excepción

  ...                // hacer algo respecto al error

  throw;             // Error!! no está en un bloque try

}

Versión correcta:

try {

  ...

  if (x) throw A();  // lanzar excepción

}

catch (A a) {        // capturar excepción

  ...                // hacer algo respecto al error

  try {

    throw;           // Ok. relanza excepción A

  }

  catch (A a) {      // capturar excepción

    ...

  }

}


Otra versión correcta:

void foo();

void main () {         // ==========

  try {                // Bloque-T1

    ...

    foo();

  }

  catch (A a) {        // L.7: capturar excepción

    ...                // L.8: hacer algo respecto al error

  }

  return 0;

}

void foo() {

  ...

  if (x) try {         // Bloque-T2

    throw A();         // L.15: Ok. lanza excepción A

  }

  catch (A) {          // L.17:

    ...                // hacer algo respecto al error

    throw;             // L.19: Ok. relanzar excepcion A

  }

}

Comentario

La excepción L.15 es capturada en L.17.

La sentencia L.19 es correcta porque este throw está en el bloque T1, y relanza la excepción A lanzada en L.15.

La excepción lanzada en L.19 es capturada en L.7, de forma que se ejecutan las sentencias L.8.

Ver ejemplo ejecutable ( Relanzar excepción)

Ejemplo 2:

Muestra como el manejador de una excepción puede relanzar la excepción recibida (en ese momento debe existir una excepción en curso).

#include <iostream>
using namespace std;

bool ok = true;
class A{};
void test();       // prototipo
void foo(bool);    // prototipo

int main() {       // ==================
  try { test(); }
  catch(A& a){ ok = true; }
  ok ? (cout << "Correcto\n") : (cout << "Incorrecto\n");
  return ok ? 1 : 0;
}
void test() {
  try { foo(true); }
  catch(A& a) {
    ok ? (cout << "Correcto\n") : (cout << "Incorrecto\n");
    ok = false;
    throw;           // se relanza la excepción A
  }
}
void foo(bool b){
  b ? (cout << "Lanzar A\n") : (cout << "No lanzar A\n");
  if(b) throw A();
}

Salida:

Lanzar A
Correcto
Correcto

Ejemplo 3:

Cuando ocurre una excepción la expresión throw inicializa un objeto temporal del tipo X correspondiente al argumento utilizado en la expresión throw(X arg).  El compilador puede generar otras copias, en consecuencia, como se muestra aquí, puede ser útil definir un constructor de copias para el objeto usado en la excepción.

#include <stdio.h>
class festival {
  public:
  festival() {        // constructor por defecto

    puts("Hacer un festival");

  }
  festival(const festival&){    // constructor copia

    puts("Copiando un festival");

  }
  ~festival() {       // destructor

    puts("Destruyendo un festival");

  }
};

int main() {          // =================
  try { puts("Lanzando un festival"); throw(festival()); }
  catch(festival&){ puts("Capturando un festival" ); }
  return 0;
}

Salida:

Lanzando un festival
Hacer un festival
Copiando un festival
Destruyendo un festival
Capturando un festival
Destruyendo un festival

  Inicio.


[1]  La sentencia funciona correctamente (puede hacerse la prueba), sin embargo esta es una de las pequeñas paradojas sintácticas del C++.  La invocación al constructor por defecto Out() teóricamente no debería producir nada en esta sentencia, ya que "por definición" el constructor de una clase se define no devolviendo nada (siguiera void).