Existe una clase base "exception" de la que podemos heredar nuestras propias clases derivadas para pasar objetos a los manipuladores. Esto nos ahorra cierto trabajo, ya que aplicando polimorfismo necesitamos un único "catch" para procesar todas las posibles excepciones.
Esta clase base de declara en el fichero de cabecera estándar "exception", y tiene la siguiente forma (muy sencilla):
class exception { public: exception() throw() { } virtual ~exception() throw(); virtual const char* what() const throw(); };
Sólo contiene tres funciones: el constructor, el destructor y la función "what", estas dos últimas virtuales. La función "what" debe devolver una cadena que indique el motivo de la excepción.
Verás que después del nombre de la función, en el caso del destructor y de la función "what" aparece un añadido "throw()", esto sirve para indicar que estas funciones no pueden producir ningún tipo de excepción, es decir, que no contienen sentencias "throw".
Podemos hacer una prueba sobre este tema, por ejemplo, crearemos un programa que copie un fichero.
#include <iostream> #include <fstream> using namespace std; void CopiaFichero(const char* Origen, const char *Destino); int main() { char Desde[] = "excepcion.cpt"; // Este fichero char Hacia[] = "excepcion.cpy"; CopiaFichero(Desde, Hacia); cin.get(); return 0; } void CopiaFichero(const char* Origen, const char *Destino) { unsigned char buffer[1024]; int leido; ifstream fe(Origen, ios::in | ios::binary); ofstream fs(Destino, ios::out | ios::binary); do { fe.read(reinterpret_cast<char *> (buffer), 1024); leido = fe.gcount(); fs.write(reinterpret_cast<char *> (buffer), leido); } while(leido); fe.close(); fs.close(); }
Ahora lo modificaremos para que se genere una excepción si el fichero origen no existe o el de destino no puede ser abierto:
#include <iostream> #include <fstream> using namespace std; // Clase derivada de "exception" para manejar excepciones // de copia de ficheros. class CopiaEx: public exception { public: CopiaEx(int mot) : exception(), motivo(mot) {} const char* what() const throw(); private: int motivo; }; const char* CopiaEx::what() const throw() { switch(motivo) { case 1: return "Fichero de origen no existe"; case 2: return "No es posible abrir el fichero de salida"; } return "Error inesperado"; } // (1) void CopiaFichero(const char* Origen, const char *Destino); int main() { char Desde[] = "excepcion.cpp"; // Este fichero char Hacia[] = "excepcion.cpy"; try { CopiaFichero(Desde, Hacia); } catch(CopiaEx &ex) { cout << ex.what() << endl; } // (2) cin.get(); return 0; } void CopiaFichero(const char* Origen, const char *Destino) { unsigned char buffer[1024]; int leido; ifstream fe(Origen, ios::in | ios::binary); if(!fe.good()) throw CopiaEx(1); // (3) ofstream fs(Destino, ios::out | ios::binary); if(!fs.good()) throw CopiaEx(2); // (4) do { fe.read(reinterpret_cast<char *> (buffer), 1024); leido = fe.gcount(); fs.write(reinterpret_cast<char *> (buffer), leido); } while(leido); fe.close(); fs.close(); }
Espero que esté claro lo que hemos hecho. En (1) hemos derivado una clase de "exception", para hacer el tratamiento de nuestras propias excepciones. Hemos redefinido la función virtual "what" para que muestre los mensajes de error que hemos predefinido para los dos posibles errores que queremos detectar.
En (2) hemos hecho el tratamiento de excepciones, propiamente dicho. Intentamos copiar el fichero, y si no es posible, mostramos el mensaje de error.
Dentro de la función "CopiaFichero" intentamos abrir el fichero de entrada, si fracasamos hacemos un "throw" con el valor 1, lo mismo con el de salida, pero con un valor 2 para "throw".
Ahora podemos intentar ver qué pasa si el fichero de entrada no existe, o si el fichero de salida existe y está protegido contra escritura.
Hay que observar que el objeto que obtenemos en el "catch" es una referencia. Esto es recomendable, ya que evitamos copiar el objeto devuelto por el "throw". Imaginemos que se trata de un objeto más grande, y que la excepción que maneja está relacionada con la memoria disponible. Si pasamos el objeto por valor estaremos obligando al programa a usar más memoria, y puede que no exista suficiente.
Cuando se derivan clases desde la clase base "exception" hay que tener cuidado en el orden en que las capturamos. Debido que se aplica polimorfismo, cualquier objeto de la jerarquía se ajustará al catch que tenga por argumento un objeto o referencia de la clase base, y sucesivamente, con cada una de las clases derivadas.
Por ejemplo, podemos crear una clase derivada de "exception", "Excep2", otra derivada de ésta, "Excep3", para hacer un tratamiento de excepciones de uno de nuestros programas:
class Excep2 : public exception {} class Excep3 :public Excep2 {} ... try { // Nuestro código } catch(Excep2&) { // tratamiento } catch(Excep1&) { // tratamiento } catch(exception&) { // tratamiento } catch(...) { // tratamiento } ...
Si usamos otro orden, perderemos la captura de determinados objetos, por ejemplo, supongamos que primero hacemos el "catch" de "exception":
class Excep2 : public exception {} class Excep3 :public Excep2 {} ... try { // Nuestro código } catch(exception&) { // tratamiento } catch(Excep2&) { // No se captura // tratamiento } catch(Excep1&) { // No se captura // tratamiento } catch(...) { // tratamiento } ...
En este caso jamás se capturará una excepción mediante "Excep2" y "Excep3".
© Agosto de 2003 Salvador Pozo, salvador@conclase.net