Manejo de excepciones

Programación en C++. Catch. Throw. Vector. Recursos. Archivos. Memoria. Almacenamiento

3 downloads 130 Views 411KB Size

Story Transcript

MANEJO DE EXCEPCIONES El manejo de excepciones se basa en que si una función detecta un problema que no puede resolver se lanza (throw) un mecanismo de tratamiento del problema. En general, se llama a una función que atrapará (catch) y se hará cargo de la excepción. Ejemplo : determinación de los intervalos de un vector class Vector { int* p; int tam; public: class intervalo{}; //clase creada para la excepción int& operator[](int i); // ..... }; Los objetos de la clase intervalo se emplean para las excepciones y se lanzan de la manera siguiente: int& Vector::operator[](int i) { if ( 0<=i && i
catch (Vector::Intervalo) { // se ejcuta esta sección del programa solo si // la llamada hacer_algo() llama a // Vector::operator[]() con un Ã−ndice erróneo } //...... por aquÃ− sigue si no existe error } La construcción catch(/*......*/) { // .....} es la que denominamos manejador de excepción. Se puede emplear inmediatamente después de un bloque que comienza con la palabra clave try ó después de otro manejador catch. Los paréntesis que posee la declaración catch contienen una declaración similar a la declaración de un argumento de función y especifica el tipo de objeto con los cuales se puede entrar al manejador y, excepcionalmente, nombra el argumento. Si hacer_algo() ó cualquier otra función a la que llama hacer_algo() provoca un error de intervalo en algún Vector el manejador atrapa la excepción y se ejecuta el código de manejo de excepciones. Por ejemplo en el siguiente caso se entrarÃ−a en el manejo de excepciones : void hacer_algo(Vector& v) { // ... caida(v); // ... } void caida(Vector& v) { v[v.tamaño()+10]; // provoca el error de intervalo } El proceso de lanzar y hacerse cargo de una excepción implica examinar la cadena de llamadas que se tiene en la pila hasta encontrar el llamador (throw), en este proceso además se trata de desarrollar y buscar en la pila hasta la función que atrapa (catch). Una vez que la función atrapa la excepción, ésta es procesada, dejando de lado los demas manejadores que puedan existir. Esto significa que solo se llama al manejador activo por el que pasó más recientemente el hilo de control.

2

Ejemplo en el que no se atrapará a Vector::Intervalo : int ff(Vector& v) { try { f(v); // f() atrapa Vector::Intervalo } catch (Vector::Intervalo) { // el manejo de la // excepción no llega aquÃ− // ... } } Si quisieramos manejar varios errores para una misma clase, por ejemplo para la clase Vector podrÃ−amos tener lo siguiente : class Vector { int* p; int tam; public: enum { max = 32000 }; class intervalo{}; // excepción para el intervalo class Tamaño {}; // excepción para el tamaño int& operator[](int i); // ..... }; Para lanzar una excepción por el tamaño tendrÃ−amos : Vector::Vector(int tam) { if (tam<0 || max < tam) throw Tamaño (); 3

// ... } Un usuario de la clase Vector podrÃ−a utilizar las dos excepciones invocando ambas dentro de un bloque try : void f() { try { usar_vectores(); } catch (Vector::Intervalo) { // ajustar el intervalo de los vectores // intentarlo de nuevo } catch (Vector::Tamaño) { cerr << “error en el tamaño del vector”; exit(99); } // por aquÃ− sigue normalmente } Los manejadores de excepciones podrÃ−an estar anidados : try { // ... } catch (xxii) { try { // algo complicado }

4

catch (xxiii) { // falló algo complicado } } Cuando se lanza una excepción se lanza un objeto, si se necesita alguna información adicional cuando se lanza la excepción, se podrÃ−a hacer colocando datos en ese objeto : class Vector { int* p; int tam; public: enum { max = 32000 }; class intervalo{ public : int Ã−ndice; Intervalo(int i) : Ã−ndice(i) {} } ; // excepción para el intervalo class Tamaño {}; //excepción creada para el tamaño int& operator[](int i); // ..... }; Para conocer el Ã−ndice erróneo el manejador debe asignar un nombre al objeto de excepción: void f (Vector& v) { // ... try { hacer_algo(v); } 5

catch (Vector::Intervalo r) { cerr<< “Ã−ndice incorrecto”<< r.Ã−ndice ,, `\n' ; // ... } } Muchas veces las excepciones pertenecen a una familia, por ejemplo podemos tener un conjunto de manejo de excepciones para manejar errores matemáticos, que incluyan desborde, insuficiencia, división por cero, etc. ,. Enum Errmatem { Desborde, Insuficiencia, Diventrecero, /*...*/}; try { ... } catch (Errmatem m) { switch (m) { case Desborde: // ... case Insuficiencia: // ... case Diventrecero: } } Otra manera de implementar esto es por medio de la utilización de clases y herencia : class Errmatem { }; class Desborde: public Errmatem { }; class Insuficiencia: public Errmatrm { }; class Diventrecero: public Errmatem{ }; // ... De esta forma uno podrÃ−a manejar cualquier Errmatem sin preocuparse del error que se trate : try { ...}

6

catch(Desborde) { // manejar Desborde o cualquier // cosa derivada de Desborde } catch (Errmatem { // manejar cualquier Errmatem que no sea Desborde } Adquisición de recursos Con frecuencia cuando se adquiere un recurso como por ejemplo cuando se abre un archivo, se asigna memoria al almacenamiento disponible, se establece un bloqueo para controlar un acceso, etc., es de vital importancia para el futuro de la ejecución de un programa que ese recurso se libere correctamente, ejemplo : void usar_archivo(const char* fn) { FILE * f = fopen (fn,”r”); // Hacer uso del archivo f fclose(f); } si sucede algo entre el fopen() y el fclose() una excepción puede hacer que salga de la función usar_archivo sin llamar a fclose(). Una primera solución que se nos podrÃ−a ocurrir es la siguiente : void usar_archivo(const char* fn) { FILE * f = fopen (fn,”r”); try { // Hacer uso del archivo f } catch (...) { // aquÃ− se atrapa la excepción se fclose(f); // cierra el archivo y se throw; // vuelve a lanzar la excepción } 7

fclose(f); } El problema del manejo de excepción anterior es que lleva mucho código y, por lo tanto, es costosa. La manera general de tratar este problema es la siguiente : void adquirir () { // adquirir recurso 1 // ... // adquirir recurso n // utilizar recursos // liberar recurso n // ... // liberar recurso 1 } Es importante, como se podrá observar, liberar los recursos en el orden inverso al que se adquirieron, de modo parecido a la creación y destrucción de objetos. Por lo tanto se podrÃ−an manejar los problemas de adquisición y liberación de recursos empleando objetos de clases con constructores y destructores. Class ApuntArch { FILE* p; public: ApuntArch(const char* n, const char* a) { p = fopen(n,a); } ApuntArch(FILE* pp) { p = pp; } ~ApuntArch() { fclose(); } operator FILE*() { return p; } } Podemos construir un objeto ApuntArch dado un File* ó por medio de los argumentos requeridos para un fopen(), el destructor actuará cuando se llegue al final del alcance de ApuntArch. El programa ahora se reduce : 8

void usar_archivo(const char* fn) { ApuntArch f(fn,”r”); // Hacer uso del archivo f } En esta implementación el archivo se cerrará independientemente de si la función sale normalmente o por el lanzamiento de una excepción . Otro problema que requiere un frecuente tratamiento es cuando fracasa el intento de adquirir recursos, esto puede pasar cuando excedemos la capacidad de abrir archivos o cuando solicitamos memoria del almacenamiento disponible y esta está agotada, ante este tipo de problemas tenemos dos posibilidades : • continuar : en este caso “alguien” debe resolver el problema • terminar : pedir mas recursos y si no se encuentra ninguno lanzar una excepción El primer caso de continuar se resuelve por medio de la programación de nuevas funciones que resuelvan el problema, en el segundo caso se resuelve le problema por medio del manejo de excepciones. Este último caso es más sencillo de manejar y resolver. # include extern void* _last_allocation extern void* operator new(size_t size) // tamaño { void* p; while ( (p=malloc(size)) ==0 ) { if (_new_handler) // resuelve le problema de new (*_new_handler)(); // llamando a esta función else return 0; } return _last_allocation=p; } Si operator new() no puede asignar memoria llama a _new_handler(), si ésta función puede resolver el problema todo esta bien , sino esta rutina no puede volver a llamar a operator new() sin causar un loop 9

infinito. Para este caso se puede optar por llamar a una excepción y dejar que este resuelva el conflicto : void mi_manejador_new() { tratar_de_hallar_memoria(); if(se_halló_memoria()) return; throw memoria_agotada(); // darse por vencido } En algún otro lugar debemos tener : try { // ...} catch (memoria_agotada) { // ... } Universidad Tecnológica Nacional - Santa Fe - Departamento Sistemas Curso : Desarrollos de Programación en C++

10

Get in touch

Social

© Copyright 2013 - 2024 MYDOKUMENT.COM - All rights reserved.