Curso de C++ v2.0
Consultas, lista de correo 'C++ Con Clase' 'C++ Con Clase' página de entrada Librerías estándar C Tabla de contenido Contactar con Webmaster
*Introducción
*1 Toma de contacto
*2 Variables I
*3 Funciones I: Declaración y definición
*4 Operadores I
*5 Sentencias
*6 Declaración de variables
*7 Normas para la notación
*8 Cadenas de caracteres
*9 Conversión de tipos
*10 Variables II: Arrays
*11 Variables III: Estructuras
*12 Variables IV: Punteros 1
*13 Operadores II: Más operadores
*14 Operadores III: Precedencia
*15 Funciones II: Parámetros por valor y referencia
*16 Variables V: Uniones
*17 Variables VI: Punteros 2
*18 Operadores IV: De bits y condicional
*19 Definición de tipos
*20 Funciones III
*21 Funciones IV: Sobrecarga
*22 Operadores V: Sobrecarga
*23 El preprocesador
*24 Funciones V: Recursividad
*25 Variables VII: Modificadores
*26 Espacios con nombre
*27 Clases I: Definiciones
*28 Declaración de clases
*29 Constructores
*30 Destructores
*31 El puntero this
*32 Sistema de protección
*33 Modificadores para miembros
*34 Más sobre funciones
*35 Operadores sobrecargados
*36 Herencia
*37 Funciones virtuales
*38 Derivación múltiple
*39 Trabajar con ficheros
*40 Plantillas
*41 Punteros a miembros
*42 Castings
*43 Excepciones
 . Clase exception
 . Orden de captura
 . Especificaciones
 . Destructores
 . Estándar
 . Relanzar excepción
*Ejemplos capítulos 1 a 6
*Ejemplos capítulos 8 y 9
*A Palabras reservadas C/C++
*B Trigrafos y símbolos alternativos
*C Librerías estándar
*D Streams
<< < > >>

43 Manejo de excepciones

Las excepciones son en realidad errores durante la ejecución. Si uno de esos errores se produce y no implementamos el manejo de excepciones, el programa sencillamente terminará abruptamente. Es muy probable que si hay ficheros abiertos no se guarde el contenido de los buffers, ni se cierren, además ciertos objetos no serán destruidos, y se producirán fugas de memoria.

En programas pequeños podemos prever las situaciones en que se pueden producir excepciones y evitarlos. Las excepciones más habituales son las de peticiones de memoria fallidas.

Veamos este ejemplo, en el que intentamos crear un array de cien millones de enteros:

#include <iostream>
using namespace std;

int main() {
   int *x = NULL;
   int y = 100000000;
   
   x = new int[y];
   x[10] = 0;
   cout << "Puntero: " << (void *) x << endl;
   delete[] x;
   
   cin.get();
   return 0;
}

El sistema operativo se quejará, y el programa terminará en el momento que intentamos asignar un valor a un elemento del array.

Podemos intentar evitar este error, comprobando el valor del puntero después del "new":

#include <iostream>
using namespace std;

int main() {
   int *x = 0;
   int y = 100000000;
   
   x = new int[y];
   if(x) {
      x[10] = 0;
      cout << "Puntero: " << (void *) x << endl;
      delete[] x;
   } else {
      cout << "Memoria insuficiente." << endl;
   }
   cin.get();
   return 0;
}

Pero esto tampoco funcionará, ya que es al procesar la sentencia que contiene el operador "new" cuando se produce la excepción. Sólo nos queda evitar peticiones de memoria que puedan fallar, pero eso no es previsible.

Sin embargo, C++ proporciona un mecanismo más potente para detectar errores de ejecución: las excepciones. Para ello disponemos de tres palabras reservadas extra: try, catch y throw, veamos un ejemplo:

#include <iostream>

using namespace std;

int main() {
   int *x;
   int y = 100000000;
   
   try {
      x = new int[y];
      x[0] = 10;
      cout << "Puntero: " << (void *) x << endl;
      delete[] x;
   }
   catch(std::bad_alloc&) {
      cout << "Memoria insuficiente" << endl;
   }
   
   cin.get();
   return 0;
}

La manipulación de excepciones consiste en transferir la ejecución del programa desde el punto donde se produce la excepción a un manipulador que coincida con el motivo de la excepción.

Como vemos en este ejemplo, un manipulador consiste en un bloque "try", donde se incluye el código que puede producir la excepción.

A continuación encontraremos uno o varios manipuladores asociados al bloque "try", cada uno de esos manipuladores empiezan con la palabra "catch", y entre paréntesis una referencia o un objeto.

En nuestro ejemplo se trata de una referencia a un objeto bad_alloc, que es el asociado a excepciones consecuencia de aplicar el operador "new".

También debe existir una expresión "throw", dentro del bloque "try". En nuestro caso es implícita, ya que se trata de una excepción estándar, pero podría haber un "throw" explícito, por ejemplo:

#include <iostream>

using namespace std;

int main() {
   try {
      throw 'x'; // valor de tipo char
   }
   catch(char c) {
      cout << "El valor de c es: " << c << endl;
   }
   catch(int n) {
      cout << "El valor de n es: " << n << endl;
   }
    
   cin.get();
   return 0;
}

El "throw" se comporta como un "return". Lo que sucede es lo siguiente: el valor devuelto por el "throw" se asigna al objeto del "catch" adecuado. En este ejemplo, al tratarse de un carácter, se asigna a la variable 'c', en el "catch" que contiene un parámetro de tipo "char".

En el caso del operador "new", si se produce una excepción, se hace un "throw" de un objeto de la clase "std::bad_alloc", y como no estamos interesados en ese objeto, sólo usamos el tipo, sin nombre.

El manipulador puede ser invocado por un "throw" que se encuentre dentro del bloque "try" asociado, o en una de las funciones llamadas desde él.

Cuando se produce una excepción se busca un manipulador apropiado en el rango del "try" actual. Si no se encuentra se retrocede al anterior, de modo recursivo, hasta encontrarlo. Cuando se encuentra se destruyen todos los objetos locales en el nivel donde se ha localizado el manipulador, y en todos los niveles por los que hemos pasado.

#include <iostream>

using namespace std;

int main() {
   try {
      try {
         try {
            throw 'x'; // valor de tipo char
         }
         catch(int i) {}
         catch(float k) {}
      }
      catch(unsigned int x) {}
   }
   catch(char c) {
      cout << "El valor de c es: " << c << endl;
   }
    
   cin.get();
   return 0;
}

En este ejemplo podemos comprobar que a pesar de haber hecho el "throw" en el tercer nivel del "try", el "catch" que lo procesa es el del primer nivel.

Los tipos de la expresión del "throw" y el especificado en el "catch" deben coincidir, o bien, el tipo del catch debe ser una clase base de la expresión del "throw". La concordancia de tipos es muy estricta, por ejemplo, no se considera como el mismo tipo "int" que "unsigned int".

Si no se encontrase ningún "catch" adecuado, se abandona el programa, del mismo modo que si se produce una excepción y no hemos hecho ningún tipo de manipulación de excepciones. Los objetos locales no se destruyen, etc.

Para evitar eso existe un "catch" general, que captura cualquier "throw" para el que no exista un "catch":

#include <iostream>

using namespace std;

int main() {
   try {
      throw 'x'; // 
   }
   catch(int c) {
      cout << "El valor de c es: " << c << endl;
   }
   catch(...) {
      cout << "Excepción imprevista" << endl;
   }
    
   cin.get();
   return 0;
}
<< < > >>
Free Web Hosting