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.2  Capturar excepciones

§1  Sinopsis

Recordemos ( 1.6) que el manejador de excepciones ("handler") es un bloque de código precedido por la palabra catch. Este bloque debe seguir inmediatamente el bloque-intento try o a otro bloque catch según el siguiente esquema:

try {                    // bloque-intento

   ...                   // posibles errores

}

catch (TipoX x) {        // capturar errores X

    ...

}

catch (TipoY y) {        // capturar errores Y

   ...

}

...                      // sigue aquí

La sintaxis es:

catch (<tipo_exc> [<nombre>]) { <sentencias> }


<tipo_exc> es el tipo de excepción que se capturará en esta sentencia. Ejemplo:

try {

  ...

  if (x > limit) throw "Overflow";

}

catch (char*) {

  cout << "Recibido error: ";

}

Eventualmente se puede añadir un identificador <nombre>, que puede ser usado en el cuerpo <sentencias> del bloque, de forma análoga a los argumentos de las funciones. Ejemplo:

struct E{ char* msg; };
...

try {

   ...

   if (x > limit) {

     E e = { "Error desconocido" };
     throw e;
   }

}
catch (E r) {
  cout << "Recibido: " << r.msg << endl;
}


Según el objeto capturado sea recibido por valor o por referencia, la forma del bloque catch es alguna de las siguientes:

catch(T t) {...}
catch(const T t) {...}
catch(T& t) {...}
catch(const T& t) {...}  // esta es la más usual

Nota:  aunque nos referimos a él como bloque-catch, en realidad su comportamiento y su estructura son muy parecidos al de una función. Aunque con diferencias, para muchos aspectos podemos pensar en él como una auténtica función. Como veremos a continuación, también aquí se establecen ciertas reglas de "congruencia de argumentos" para ver que bloque-catch responde a una excepción determinada.

§2  Concordancia

Debe existir un manejador para cada excepción distinta que pueda lanzar el programa. El manejador captura una excepción cuando el tipo de esta coincide (según ciertas reglas ) con el tipo de <tipo_exc>. Una vez que se ha producido la concordancia, la pila es descargada hasta el punto del "handler" al que se transfiere el control. Es entonces el manejador el que decide el tratamiento adecuado a la anormalidad detectada.

Ejemplo:

try {                    // bloque-intento

   ...                   // posibles errores overflow o aritmetic

   if (...) throw Overflow;

   ...

   if (...) throw Aritmetic;

   ...

}

catch (Overflow, o) {    // capturar errores overflow

    ...

}

catch (Aritmetic, a) {   // capturar errores aritméticos

   ...

}

...                      // sigue aquí si no hay errores


§2.1
  En caso de no existir un manejador adecuado a una excepción determinada, se desencadena un protocolo que, por defecto, produce sin más la finalización del programa ( 1.6.3 Excepciones imprevistas).

§2.2  Reglas de concordancia

La excepción es capturada por el bloque-catch cuyo argumento coincida con el tipo de objeto lanzado por la sentencia throw.  La búsqueda de coincidencia se realiza sucesivamente sobre los bloques catch en el orden en que aparecen en el código hasta que aparece la primera concordancia. Después que se ejecuta este bloque, el programa continúa su ejecución después del último de los manejadores que sigan al bloque try que lanzó la excepción, sin que se realice ulterior evaluación de otros posibles manejadores para la excepción lanzada. Por consiguiente, el orden de colocación de los bloques catch es determinante. Por ejemplo: si se incluye un manejador universal , debería ser el último.

La concordancia sigue ciertas reglas [1].  El objeto e lanzado por la sentencia throw E() será capturado por un bloque catch(C c) si se cumple alguna de las siguientes condiciones:

  • E y C son del mismo tipo.

  • C es una super-clase de E visible en el punto de lanzamiento de la excepción. Por esta razón, cuando se captura una excepción y esta pertenece a una jerarquía de clases, hay que comenzar por capturar la clase más derivada (ver Ejemplo ).

  • C y E son punteros a clases de una misma jerarquía, y existe una conversión estándar de E a C ( 4.11.2b1) en el punto de lanzamiento de la excepción.

Suponiendo que:

D* E;    // E es un puntero-a-tipoD

S* C;    // C es un puntero-a-tipoS

Para que exista una conversión estándar de E a C , debe cumplirse alguna de las condiciones siguientes:

  • C es del mismo tipo que E, aunque puede tener un especificador const o volatile.

  • C es un puntero-a-void (tipo void*).

  • B es una superclase de D en la que sus miembros pueden referenciar sin ambigüedad a los de B. (esto solo es de aplicación en los casos de herencia múltiple).

Nota:  De la segunda condición se deduce que cualquier excepción E capturada por un puntero puede ser también capturada por un puntero-a-void (el tipo void* funciona como un capturador universal de punteros).


La norma subyacente bajo las condiciones anteriores es que E y C deben coincidir exactamente, o la excepción E capturada, debe ser derivada del parámetro C del "catcher".

El siguiente ejemplo termina sin que se capture la excepción

...
class B {};
class C {};
void fun() { throw B; }  // se lanza un tipo B


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

  try {
    fun();
  }
  catch(C) {
    cout << "Capturada excepción C" << endl;

    abort();
  }

}

En cambio en el que sigue si es capturada:

...
class B {};
class D : public B {};

void fun() { throw D(); }

main() {   // ==========
  try {
    fun();
  }
  catch(B) {
    cout << "Capturada excepción B" << endl;
  }
}

§2.3  El manejador universal:

Existe la posibilidad de definir un manejador que capture cualquier excepción mediante la sintaxis siguiente:

catch (...) { <sentencias> }

Ejemplo:

En el programa que sigue la sentencia catch captura cualquier excepción con independencia de su tipo. Solo existe un catch para el bloque try.

#include <stdio.h>
bool pass;
class Out{};

void festival(bool firsttime) { if(firsttime) throw Out(); }
void test()
   try { festival(true); }
   catch(...){ pass = true; }  // puede capturar cualquier excepción
}

int main() {                    // ==================
   pass = false;
   test();
   return pass ? (puts("Excepción capturada"),0) :
                 (puts("no hay excepción") ,1);

}

Salida:

Excepción capturada

§3  Salto a una etiqueta

Se puede utilizar un goto para transferir el control del programa fuera de un manejador. Para ilustrarlo utilizaremos una versión modificada del ejemplo ya comentado (1.6.1).

#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 (captura la excepción)

     goto fallo

   }                    // L.13:
   return (puts("Acierto!"),0);

   fallo:

   return (puts("Fallo!"),1);
}

 

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

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

}

§4  Excepciones anidadas

El sistema de control de excepciones puede ser anidado a cualquier nivel (pueden existir bloques try dentro de bloques try y de bloques catch)  Como debe mantenerse la regla de que un bloque try debe ser seguido inevitablemente por un catch, lo anterior significa que pueden existir secuencias try-catch dentro de bloques try y de bloques catch.

Secuencias anidadas en el bloque-intento.

class Error { };

void func () {

   ...

   try {              // I1 Bloque-intento 1

      ...

      try {           // I1I1 Bloque-intento en I1

         ...

      }

      catch {         // I1H1 Handler de I1I1: capturar excepciones de I1I1

         ...

      }

      ...             // continúa bloque I1

   }

   catch (Error) {    // H1 Handler de I1: capturar excepciones de I1

     ...

   }

   ...                // sigue a bloque I1

}


Secuencias anidadas en el bloque-manejador

class Error { };

void func () {

   ...

   try {              // I1 Bloque-intento 1

      ...

   }

   catch (Error) {    // H1 Handler de I1: capturar excepciones de I1

      ...

      try {           // H1I1 Bloque-intento en Handler-H1

         ...

      }

      catch {         // H1H1 Handler de H1I1: capturar excepciones de H1I1

         ...

      }

      ...             // continúa handler H1

   }

   ...                // sigue a bloque I1

}

Ejemplo 1:

#include <stdio.h>
class festival{};
class Verano : public festival{};
class Primavera: public festival{};
void fiesta(int);

 

int main() {           // ====================
  try { fiesta(0); }
  catch(const Verano&) { puts("Festival de Verano"); }
  catch(const Primavera&) { puts("Festival de Primavera" ); }


  try { fiesta(1); }
  catch(const Verano&) { puts("Festival de Verano"); }
  catch(const Primavera&) { puts("Festival de Primavera" ); }
  return 0;
}

void fiesta(int i) {
   if(i==1) throw( Verano() );
   else throw( Primavera() );
}

Resultado:

Festival de Primavera

Festival de Verano

Ejemplo 2:

El ejemplo que sigue muestra que cuando se captura una excepción y esta pertenece a una jerarquía de clases, hay que comenzar por la clase más derivada, pues de lo contrario se pierde capacidad de discriminación del tipo de excepción ocurrido.

#include <stdio.h>

class festival{};
class Verano : public festival{};
class Primavera: public festival{};

void fiesta(int i) {
   if (i==1) throw(Verano() );
   else if(i==2) throw(Primavera());
   else throw(festival() );
}


int main() {                // ====================
   try { fiesta(0); }
   // estas sentencias están en el orden adecuado
   catch(const Verano& )  { puts("Festival de Verano"); }
   catch(const Primavera&){ puts("Festival de Primavera" ); }
   catch(const festival& ){ puts("Festival" ); }


   try { fiesta(1); }
   catch(const Verano& )  { puts("Festival de Verano"); }
   catch(const Primavera&){ puts("Festival de Primavera" ); }
   catch(const festival& ){ puts("Festival" ); }


   try { fiesta(2); }
   catch(const Verano& )  { puts("Festival de Verano"); }
   catch(const Primavera&){ puts("Festival de Primavera" ); }
   catch(const festival& ){ puts("Festival" ); }


/* Si se captura la clase base primero se pierde la posibilidad de comprobar la sub-clase de la excepción que ha sido lanzada realmente */


   try { fiesta(1); }
   catch(const festival& ){ puts("Festival (de que tipo??!!)"); }
   catch(const Verano& )  { puts("Festival de Verano" ); }
   catch(const Primavera&){ puts("Festival de Primavera" ); }


   try { fiesta(2); }
   catch(const festival& ){ puts("Festival (de que tipo?!!!)"); }
   catch(const Verano& )  { puts("Festival de Verano" ); }
   catch(const Primavera&){ puts("Festival de Primavera" ); }
   return 0;
}

Salida:

Festival
Festival de Verano
Festival de Primavera
Festival (de que tipo??!!)
Festival (de que tipo?!!!)

Ejemplo 3

Una posible alternativa al diseño anterior permitiría capturar solo las excepciones de la clase-base y utilizar las propiedades del polimorfismo ( 4.11.8) para realizar la discriminación:

#include <iostream>
using namespace std;

class Festival {
  public:
  virtual void foo() { cout << "Festival" << endl; }
};
class Verano : public Festival {
  public:
  void foo() { cout << "Festival de Verano" << endl; }
};
class Primavera: public Festival {
  public:
  void foo() { cout << "Festival de Primavera" << endl; }
};

void fiesta(int i) {
  if (i==1) throw(Verano() );
  else if(i==2) throw(Primavera());
  else throw(Festival() );
}

int main() {     // ====================
  try { fiesta(0); }
  catch(Festival& f) { f.foo(); }

  try { fiesta(1); }
  catch(Festival& f) { f.foo(); }

  try { fiesta(2); }

  catch(Festival& f) { f.foo(); }

  return 0;
}

Salida:

Festival
Festival de Verano
Festival de Primavera

  Inicio.


[1]  Esta reglas son diferentes de las que sigue el compilador para encontrar la mejor concordancia en caso de sobrecarga de funciones ( 4.4.1a).