Programación en C++/Excepciones

De Wikilibros, la colección de libros de texto de contenido libre.
Plantillas Librería Estándar de Plantillas

Excepciones. Motivación histórica[editar]

Primeramente, antes de entrar en el tema de las excepciones en programación, se ha de matizar en el concepto de qué son las excepciones, vistas desde un punto de vista fuera y dentro del mundo de la programación.

En el lenguaje humano, una excepción es un elemento excluyente de una regla, y de forma convencional se ha extendido esta definición. En el lenguaje máquina, una excepción se trata, de forma general, de algo que no se espera que ocurra, pero que puede ocurrir, similar al tratamiento de errores, pero de los errores en tiempo de ejecución.

A veces estas excepciones, para una máquina, no son casos que no deberían estar contemplados, tal y como un programador se lo asigna, sino que pueden ser indicadores para comprobar que realmente todo está marchando bien o no.

En los programas de ordenador hechos en C existió durante mucho tiempo la costumbre de usar el comando "goto" (también implementada en C++), pero éste se ha ido eliminando progresivamente de casi todos y cada uno de los códigos y programas que han ido surgiendo. El significado de la función 'goto' no forma parte del libro actual, pero se pueden buscar referencias por internet donde se especifique con más detalle qué es.

Como una de las formas de control de errores más usuales era con goto, se usaron otras variantes, como las aserciones de código (assertions, en inglés) o, con la llegada de la programación orientada a objetos, de los comandos try, catch y throw.

Conformación de los bloques try y catch[editar]

Por norma general, los comandos try y catch conforman bloques de código.

Cada uno de estos bloques se recomienda, aunque sea de una única línea, envolverlos en llaves, como muestra el siguiente ejemplo:

 // ...código previo...
 try
 {
   // bloque de código a comprobar
 }
 catch( tipo )  // Ha ocurrido un suceso en el try que se ha terminado
 //la ejecución del bloque y catch recoge y analiza lo sucedido
 {
   // bloque de código que analiza lo que ha lanzado el try
 }
 // ...código posterior...

Generalmente entre el try y el catch no se suele insertar código, pero se insta al lector a que lo intente con su compilador habitual y que compruebe si hay errores o no de compilación.

Control de excepciones[editar]

Una excepción es un error que puede ocurrir debido a una mala entrada por parte del usuario, un mal funcionamiento en el hardware, un argumento inválido para un cálculo matemático, etc. Para remediar esto, el programador debe estar atento y escribir los algoritmos necesarios para evitar a toda costa que un error de excepción pueda hacer que el programa se interrumpa de manera inesperada. C++ soporta una forma más directa y fácil de ver tanto para el programador como para los revisores del código en el manejo de excepciones que su símil en el C estándar y esta consiste, tratándose del lenguaje C++, en el mecanismo try, throw y catch.


La lógica del mecanismo mencionado consiste en:

  1. Dentro de un bloque try se pretende evaluar una o más expresiones y si dentro de dicho bloque se produce un algo que no se espera se lanza por medio de throw una excepción, la misma que deberá ser capturada por un catch específico.

  2. Puesto que desde un bloque try pueden ser lanzados diferentes tipos de errores de excepción es que puede haber más de un catch para capturar a cada uno de los mismos.

  3. Si desde un try se lanza una excepción y no existe el mecanismo catch para tratar dicha excepción el programa se interumpirá abruptamente despues de haber pasado por todos los catchs que se hayan definido y de no haber encontrado el adecuado.

  4. Los tipos de excepciones lazados pueden ser de un tipo primitivo tal como: int, float, char, etc. aunque normalmente las exepciones son lanzadas por alguna clase escrita por el usuario o por una clase de las que vienen incluidas con el compilador.

En el programa que se listará a continuación muestra un ejemplo de como lanzar una excepción de tipo int dentro del bloque try, y cómo capturar la excepción por medio de catch.
Ejemplo

 // Demostración de los comandos try, throw y catch
 #include <iostream>
 
 // Función: main
 // Recibe: void
 // Devuelve: int
 // En la función principal se tratarán los comandos try, throw y catch
 int main(void)
 {
  try // Se intenta hacer el siguiente código
  {
   // Aquí puede ir más código...
   throw 125; //...aunque directamente en este caso se lanza una excepción.
  }
  catch(int) // Se captura con un catch de enteros (podría usarse long o char, por ejemplo)
  {
   std::cout << "Ha surgido una excepción de tipo entero" << std::endl; // y se muestra por pantalla
  }
  
  std::cin.get(); // Y el programa finaliza.
  
  return 0;
 }

Excepciones genéricas[editar]

Como ya se ha mencionado, los errores pueden deberse a una multitud de situaciones muchas veces inesperadas, por tal motivo, en C++ existe una forma de manejar excepciones desconocidas ( genéricas ) y es buena idea que si se está escribiendo un controlador de excepciones incluya un catch para capturar excepciones inesperadas. Por ejemplo, en el siguiente progama se escribe un catch que tratará de capturar cualquier excepción inesperada.

// Demostración: try, throw y catch
#include <iostream>

using namespace std;

int main()
{
    try {
        throw 125;
        }
    catch(...) {
        cout << "Ha ocurrido un error inesperado..." << endl;
        }
    cin.get();
    return 0;
}

Excepciones de clases[editar]

Si usted está usando una clase escrita por terceras personas o de las que se incluyen con el compilador y desea utilizar el mecanismo try, deberá conocer el tipo de excepción lanzado por dicha clase para así poder escribir el catch correspondiente para el tratamiento de dicho error. Por ejemplo, la función at() de la clase string ( que será estudiada más adelante ) lanza una excepción cuando se trata de leer o escribir un componente fuera de rango. En tales casos usted puede proceder a capturar el error como se muestra en el siguiente programa.

// Compilado y probado exitosamente con Dev-C++
// Demostración: excepción de la clase string

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s = "Hola";

    try {
        cout << s.at(100) << endl;
        }
    catch(exception& e) {
        cout << e.what() << endl;
    }

    cin.get();
    return 0;
}

En el programa anterior el método at de la clase string lanzará ( throw-up ) un error de excepción, ya que la instrucción s.at(100) trata de acceder a un elemento fuera de los limites del objeto ( s ).

La clase exception[editar]

Tal como se mostró en el programa anterior, los errorres generados por las librerías estándar de C++ pueden ser capturados por un catch que tome un parámetro tipo exception. Realmente, exception es una clase base de donde usted puede derivar las suyas y sobrescribir los métodos para el tratamiento de excepciones. La clase exception está incluida en la libreria <exception> y su estructura es la siguiente:

class exception {
public:
    exception() throw() { }
    virtual ~exception() throw();
    virtual const char* what() const throw();
};

En muchos casos bastará con sobrescribir el método what() en la clase derivada de exception, ya que dicho método es el encargado de generar el mensaje que trata de explicar la naturaleza del error ocurrido. En el programa que veremos en seguida, se da un ejemplo sencillo de cómo crear una clase derivada de la clase exception con el objetivo de sobrescribir el método what().

// Demostración: sobrescritura del método what()
#include <iostream>
#include <cstdlib>
#include <exception>

using namespace std;

class div_cero : public exception
{
public:
    const char* what() const throw()
    {
        return "Error: división por cero...";
    }
};

int main(int argc, char *argv[])
{
    double N, D;
    cout << "Probando división" << endl;
    cout << "Ingrese el numerador :";
    cin  >> N;
    cin.clear();

    cout << "Ingrese el denominador :";
    cin  >> D;
    cin.clear();

    try {
        if (D == 0) throw div_cero();
        cout << N << " / " << D << " = " << N/D << endl;
        }
    catch(exception& e) {
        cout << e.what() << endl;
    }

    system("PAUSE");
    return 0;
}

Siguiendo la misma metodología mostrada por el programa anterior, usted puede crear clases independientes para capturar errores de excepciones específicas. Por ejemplo, si se desea crear una serie de funciones matemáticas se deben considerar los sucesos de errores tales como: División por cero, Error de dominio, Error de rango, etc. Así, el siguiente programa puede ser un buen ejemplo para que usted escriba sus propios controladores de mensajes de error. Observe cómo en el programa se crea la clase ErrorMat y dentro de la misma la función porque() la cual se encargará de desplegar el mensaje de error. Aunque ErrorMat solo ha sido pensada para tratar los posibles errores de rango y errores de dominio, la misma puede rescribirse para capturar todos los errores posibles que puedan resultar a raiz de operaciones matemáticas.

Nota: No deje de observar también, cómo la función independiente logaritmo() verifica si el parámetro pasado a la misma es 0 o menor que 0 y en tales circunstancias se lanzaría (throw) un error de excepción del tipo ErrorMat ya que el dominio para la función log() es el de los números positivos y el logaritmo de cero no está definido en los números reales.

// Demostración: try, throw y catch

#include <iostream>
#include <cmath>

using namespace std;

static const int EDOMINIO=100;
static const int ERANGO=101;

class ErrorMat
{
public:
    ErrorMat() : motivo(0) {};
    ErrorMat(int m) : motivo(m) {};
    const char* porque() const throw();

private:
    int motivo;
};

const char* ErrorMat::porque() const throw()
{
    switch (motivo)
    {
    case EDOMINIO: return "Error de Dominio ";break;
    case ERANGO:   return "Error de Rango ";break;
    default:       return "Error Desconocido";  //En rigor no debería ocurrir tal Excepción 
    }	
}

double logaritmo(const double n)
{
    try {
        if (n < 0) throw(ErrorMat(EDOMINIO) );
        if (n == 0) throw(ErrorMat(ERANGO) );
        return log(n);
	}
    catch(ErrorMat& e) {
        cout << e.porque();
    }
    return 0;
}

int main()
{
    double r = 100;
    cout << "log(" << r << ") = " << logaritmo(r) << endl;
    cout << "log(-" << r << ") = " << logaritmo(-r) << endl;
    cin.get();
    return 0;
}

for(d/dx r,d/dx r>=double,r++){ if(double&&int>=cacho){ return try; } }

Plantillas Arriba Librería Estándar de Plantillas