1.6.5 Excepciones en la práctica
§1 Sinopsis
Como se ha señalado anteriormente, en nuestras aplicaciones podemos prever la utilización de excepciones que lancen cualquier tipo de objeto. Por ejemplo, un int:
int exception1 = 1;
int exception2 = 2;
...
try {
...
if ( /* cond-1 */ ) throw exception1;
if ( /* cond-2 */ ) throw exception2;
...
}
catch (int e) {
if (e = 1) { /* action 1 */ }
else if (e = 2) { /* action 2 */ }
}
Sin embargo, salvo aplicaciones muy básicas, casi diríamos de
experimentación, en la práctica estaremos usando librerías que a su vez
lancen excepciones. De modo, que lo mejor es utilizar objetos de una clase
definida por nosotros que derive de la excepción estándar, de la que a su vez podemos
derivar tantas clases como aconsejen las circunstancias. De esta
forma, podemos utilizar las mismas rutinas de captura para manejar nuestras
excepciones y las que sean provocadas por las librerías.
Por ejemplo, supongamos que estamos programando las rutinas de comunicaciones de una aplicación Windows en la que empleamos las librerías Winsock, muchas de cuyas funciones lanzan excepciones en caso de error o circunstancias anómalas -por cierto muy frecuentes en las comunicaciones TCP/IP-. En este caso, para nuestras rutinas de comunicación podríamos utilizar una clase con el siguiente diseño:
class TCPexception : public std::exception {
std::string message;
public:
TCPexception () :
message("Error en conexion TCP/IP") {}
TCPexception (const std::string& str) :
message("Error en conexion TCP/IP:\n" + str) {}
virtual const char* what() const throw() {
return message.c_str();
}
};
Como puede verse, la clase incluye un constructor explícito sin argumentos que
construye un mensaje estándar y otro con un parámetro que permite añadir una aclaración adicional según el caso. Observe que el método what()
que nos permitirá interrogar el mensaje del objeto capturado, goza de las
siguientes características:
- Es virtual -> pueden existir otras definiciones en clases derivadas
- Devuelve un puntero a cadena de caracteres constante.
- No puede lanzar excepciones
- No puede modificar ninguna propiedad en la clase.
Con este diseño, podemos lanzar excepciones cuando las circunstancias lo
exijan. Por ejemplo:
if ( /* condition-1 */ )
throw TCPexception();
else if ( /* condition-2 */ )
throw TCPexception("Connection closed by remote peer");
En estas circunstancias, nuestras operaciones de
comunicación podrían estar controladas de la siguiente forma [1]:
try {
/* rutinas de comunicacion */
}
catch (std::exception& e) { // capturador-1
std::cerr << e.what();
}
catch(...) {
// capturador genérico
std::cerr << "Unhandled exception";
}
Según se indicó al tratar de las reglas de concordancia para la captura de
excepciones ( 1.6.2),
un diseño como el presentado tiene la ventaja de que el capturador-1
permitirá la captura de nuestras excepciones junto con las posibles excepciones
estándar que pudieran ser lanzadas por las librerías utilizadas -si están
bien diseñadas, probablemente sus excepciones sean también derivadas de las
excepciones estándar-, mientras que el capturador genérico que sigue,
permitiría capturar todas las demás.
Respecto a la discriminación entre nuestras ecepciones y las que pudieran ser lanzadas por la librería, no existirá ningún problema al respecto según quedó demostrado en el ejemplo-3 de la página anterior ( 1.6.2).
[1] Recordemos que en opinión del Dr. Stroustrup, el mecanismo C++ de excepciones no está pensado para controlar anomalías en funciones individuales sino en subsistemas completos.