La clase "exception"
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.t"; // Este fichero
char Hacia[] = "excepcion.y";
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.p"; // Este fichero
char Hacia[] = "excepcion.y";
try {
CopiaFichero(Desde, Hacia);
}
catch(CopiaEx &ex) {
cout << ex.what() << endl;
} // (2)
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.
Orden en la captura de excepciones
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".
Ir al Principio
Comentarios