Excepciones

Site: Facultad de Ingeniería U.Na.M.
Course: Computación ET-344
Book: Excepciones
Printed by: Guest user
Date: Wednesday, 3 July 2024, 6:32 AM

1. Introducción

Este tema es informativo, no forma parte de la materia, pero sucede que los alumnos utilizan excepciones en Python y luego tratan de usar el mismo concepto el C++. 

En C++ el tema tiene otras aristas que intentaremos ilustrar, sin ahondar en demasiados detalles.



2. Interrupciones (eventos generados por hardware)

Este tema es informativo, no forma parte de la materia, pero sucede que los alumnos utilizan excepciones en Python y luego tratan de usar el mismo concepto el C++. 

En C++ el tema tiene otras aristas que intentaremos ilustrar, sin ahondar en demasiados detalles.


Interrupciones 

Como sugiere el nombre, las señales de interrupción proporcionan una forma de desviar el procesador al código fuera del flujo normal de control.

Cada dispositivo de hardware es capaz de emitir solicitudes de interrupción generalmente tiene un línea o  pin  de salida única designada como línea de Solicitud de interrupción (IRQ). Estas líneas están conectadas a los pines de entrada de un circuito de hardware llamado controlador de interrupción programable , I/O Advanced Programmable Interrupt Controller (I/O APIC).


Se pueden ver que hay interrupciones generadas por las CPUs e interrupciones externas.

Cuando llega una señal de interrupción, la CPU debe detener lo que está haciendo actualmente y cambiar a una nueva actividad; lo hace guardando el valor actual del contador del programa (es decir, el contenido de los registros, memoria de trabajo del proceso) en la pila del Modo Kernel y colocando una dirección relacionada con el tipo de interrupción en el contador del programa.

Las interrupciones se caracterizan por que:

  • Llegan en cualquier momento.
  • Pueden ocurrir mientras otra interrupción está en curso.

Hay interruciones enmascarables y no mascarables.

Interrupciones enmascarables

Todas las solicitudes de interrupción (IRQ) emitidas por dispositivos de E/S dan lugar a enmascarables. Una interrupción enmascarable puede estar en dos estados: enmascarada o desenmascarada. La unidad de control ignora una interrupción enmascarada mientras permanezca enmascarado.

Interrupciones no enmascarables

Sólo unos pocos eventos críticos (como fallas de hardware) dan lugar a interrupciones no enmascarables.

Un integrado que hace estas tareas ejecutar el código de cada interrupción es el 8259A  de intel.

8259A Controlador de Interrupciones Programables.



3. Excepciones (eventos generados por software)

Excepciones.

A) Excepciones detectadas por el procesador

Cuando la CPU detecta una condición anómala durante la ejecución una instrucción. Estos se dividen a su vez en tres grupos, dependiendo de el valor del registro EIP (EIP es un registro apunta a la siguiente instrucción que debe ejecutarse en el flujo de ejecución del programa) que se guarda en la pila del modo Kernel cuando la unidad de control de la CPU plantea la excepción.

  1. Fault ,Fallos:Generalmente se puede corregir; una vez corregido, el programa puede reiniciar sin pérdida de continuidad continuando en el valor guardado de EIP siempre que sea capaz de corregir la condición anómala que provocó interrupción.
  2. Trap, Trampas: Informado inmediatamente después de la ejecución de las instrucciones de captura. Después de que el kernel devuelve el control al programa, se le permite continuar su ejecución sin pérdida de continuidad en el valor guardado de EIP. El uso principal de las trampas es para fines de depuración. El papel de la señal de interrupción en este caso es notificar al depurador que se ha ejecutado una instrucción específica (por ejemplo, se ha alcanzado un punto de interrupción brakepoint dentro de un programa).

  3. Abort, Aborta : Se produjo un error grave; la unidad de control tiene problemas y puede ser que no se puede almacenar en el registro EIP. Los abortos se utilizan para informar errores graves, como fallas de hardware y valores no válidos o inconsistentes en las tablas del sistema. Este El controlador no tiene más remedio que forzar la finalización del proceso afectado.


B) Excepciones programadas

Ocurren a petición del programador, deberían se pensadas por el programador, son activados por int (interrupciones) o int3.

La instrucción int3 es una instrucción de interrupción de software que se utiliza comúnmente para propósitos de depuración en programas en lenguaje ensamblador y en entornos de desarrollo de software. En arquitecturas x86 y x86-64, la instrucción int3 provoca una excepción de interrupción de software que suele ser manejada por un depurador.  

Las excepciones programadas son manejadas por la unidad de control como trampas; A menudo se les llama interrupciones de software. Las excepciones tienen dos usos comunes:

  1. implementar llamadas al sistema
  2. notificar un depurador de un evento específico.

Nota del Docente:

El lenguaje Python, suele se mas usado para el desarrollo de aplicaciones en las que el error en el ingreso de datos puede generar una excepción. Esto no sucede en C++, desde nuestro enfoque en la materia, la misma es aprender C++ para facilitar la lectura de datos de sensores, datos registrados en archivos, en el ingreso de datos por parte del usuario para C++ asumimos que normalmente no hay errores de tipo ( se simula que se leen desde un sensor) por eso no vemos como trata C++ las excepciones en profundidad. Pero a modo de información, mostraremos este tema de manera superficial.

Una excepción es la indicación de un problema que ocurre durante la ejecución de un programa.

El manejo de excepciones está diseñado para procesar errores síncronos, que ocurren cuando se ejecuta una instrucción.

Ejemplos comunes de estos errores son :

  • los subíndices de arreglos fuera de rango

  • el desbordamiento aritmético (es decir, un valor fuera del rango de valores representables)

  • la división entre cero

  • los parámetros de función inválidos

  • la asignación fallida de memoria (debido a la falta de memoria).

El manejo de excepciones proporciona un mecanismo estándar para procesar los errores

Si los problemas potenciales ocurren con poca frecuencia, al entremezclar la lógica del programa y la lógica del manejo de errores se puede degradar el rendimiento del programa, ya que éste debe realizar pruebas (tal vez con frecuencia) para determinar si la tarea se ejecutó en forma correcta, y si se puede llevar a cabo la siguiente tarea, podemos decir que se ejecuta el código pero con recaudos que hacen que el rendimiento no sea el mejor.

El nombre “excepción” implica que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción generalmente se ejecuta en forma correcta, entonces la “excepción a la regla” es cuando ocurre un problema.

Los programadores deben escribir programas tolerantes a fallas y robustos, que puedan tratar con los problemas que puedan surgir sin dejar de ejecutarse, o que terminen de una manera no dañina.

Se pueden definir una clase de excepción para representar el tipo del problema que podría ocurrir, en nuestro caso vamos a mostrar ejemplos de excepciones estándares y no estándares.

Gestión de la memoria (Stack y Heap) en C++

Cuando ejecutamos un programa, nuestro sistema operativo le asigna un proceso con algo de memoria, llamada la memoria virtual, donde ejecutarse. Esa memoria virtual se divide en segmentos o áreas:



  • El segmento de texto, conocido como segmento de código, contiene las instrucciones ejecutables y es solo de lectura: https://en.wikipedia.org/wiki/Code_segment.
  • El segmento de datos contiene la inicialización de las variables estáticas, globales y estáticas locales: https://en.wikipedia.org/wiki/Data_segment.
  • El segmento heap (montón) contiene memoria reservada dinámicamente por el programador para almacenar variables temporales en tiempo de ejecución. Es un segmento compartido por todos los subprocesos, bibliotecas compartidas y módulos cargados dinámicamente en un proceso: https://en.wikipedia.org/wiki/Manual_memory_management.
  • El segmento stack (pila), ubicado en la parte superior de la memoria, contiene la pila de llamadas (call stack). Se trata de una estructura LIFO (último en llegar primero en salir) donde se almacenan los argumentos pasados al programa, las cadenas del entorno, argumentos de las funciones, variables locales no inicializadas, además de almacenar el registro de llamada de funciones y el retorno: https://en.wikipedia.org/wiki/Stack-based_memory_allocation.

Diferencias entre stack y heap

Los segmentos más interesantes para nosotros son el montón (heap) y la pila (stack), así que voy a resumir sus diferencias clave en una tabla:



¿Cuándo utilizar el montón y la pila?

  • El montón (heap) debe utilizarse cuando se necesita asignar un gran bloque de memoria. Por ejemplo crear una matriz de gran tamaño o una estructura demasiado grande para mantener una variable durante mucho tiempo.

  • La pila (stack) es mejor utilizarla cuando se trabaja con variables relativamente pequeñas que solo se requieren mientras una función está viva, debido a que provee un acceso más fácil y rápido.

¿Por que mencionamos esto?

La estructura de datos de una aplicación creadas por funciones o programas utilizan distinto tipos de memoria, y ante la terminación no programada u ocasionada por una excepción hay que crear una destrucción de los datos de la  pila de ejecución del programa. 
Cuando una excepción ocurre, TODAS las funciones que se usaron o llamaron para llegar al lugar donde ocurre la excepción DEBEN ser tratar o interrumpir su ejecución. 
Cuando decimos TODOS , nos referimos a los que se encuentran en el Call Stack.
Es por eso que existen dos estados:
  1. Ejecución normal
  2. Estado de Pánico, que es cuando se dispara una excepción o cadenas de excepciones, ya que las funciones suelen estar enganchadas, unas con otras.

Buenas prácticas

  • Utilizar excepciones para errores inesperados: No para errores de lógica o flujo de control.
  • Documentar las excepciones: Especificar el tipo de excepción y su significado.
  • Lanzar excepciones específicas: Usar clases derivadas de std::exception para mayor precisión.
  • Evitar la anidación excesiva de bloques try/catch: Simplifica el código y facilita la depuración.

4. Resumen

En el contexto del sistema operativo Linux, las interrupciones y las excepciones son dos conceptos distintos pero relacionados en el manejo de eventos imprevistos o asincrónicos que pueden ocurrir durante la ejecución de un programa. Aquí hay una descripción de las diferencias clave entre ellas:

  1. Interrupciones (eventos generados por hardware)

    • Las interrupciones son eventos externos o hardware generados por dispositivos periféricos o el propio hardware del sistema y se corresponde con señales eléctricas generadas por el hardware.
    • Pueden ser síncronas o asíncronas. Las interrupciones síncronas ocurren en momentos específicos del programa, mientras que las asíncronas pueden ocurrir en cualquier momento.
    • Las interrupciones pueden ser enmascarables o no enmascarables. Las enmascarables pueden ser deshabilitadas temporalmente por el sistema operativo, mientras que las no enmascarables no pueden ser deshabilitadas.
    • Ejemplos de interrupciones son la interrupción del temporizador (timer interrupt), interrupciones de dispositivos de entrada/salida (I/O), interrupción de reloj en tiempo real (RTC), etc.
    • Las interrupciones se manejan mediante rutinas de manejo de interrupciones (interrupt handlers), que son funciones de bajo nivel escritas específicamente para responder a eventos de interrupción.

  2. Excepciones (eventos generados por software)

    • Las excepciones son eventos internos o software generados por el propio procesador durante la ejecución de instrucciones.
    • Generalmente son causadas por condiciones anómalas durante la ejecución del programa, como divisiones por cero, acceso a memoria no válida, instrucciones ilegales, entre otros.
    • Las excepciones pueden ser síncronas, ya que están directamente relacionadas con la ejecución de instrucciones del programa.
    • Las excepciones son manejadas por el sistema operativo o por el propio programa, dependiendo de la naturaleza de la excepción y de cómo se haya configurado el sistema.
    • En Linux, las excepciones son manejadas principalmente por el kernel del sistema operativo, que puede tomar medidas como enviar señales al proceso, abortar la ejecución del proceso o realizar otras acciones específicas para manejar la excepción.

En resumen, mientras que las interrupciones son eventos externos generados por hardware o dispositivos periféricos, las excepciones son eventos internos generados por el propio procesador debido a condiciones anómalas durante la ejecución del programa.

Ambos tipos de eventos requieren manejo especial por parte del sistema operativo para garantizar un funcionamiento adecuado del sistema.


5. Excepciones

Este tema es en particular el que queremos profundizar en la cátedra de C++

En nuestro caso vamos concentrarnos en las Excepciones, por eventos internos o software generados por el propio procesador durante la ejecución de instrucciones y las maneja el kernel del sistema operativo.
El sistema operativo Linux interpreta la mayoría de las excepciones emitidas por la CPU como condiciones de error. Cuando ocurre uno de ellos, el núcleo envía una señal al proceso que causó el excepción para notificarle una condición anómala. Si, por ejemplo, un proceso realiza una división por cero, la CPU genera una excepción de "Error de división" y el correspondiente
El controlador de excepciones envía una señal SIGFPE al proceso actual, que luego toma la pasos necesarios para recuperar o (si no hay ningún controlador de señal configurado para esa señal) cancelar.

La señal SIGFPE es una señal de excepción que se utiliza en sistemas Unix y Unix-like (como Linux) para manejar errores relacionados con operaciones aritméticas. SIGFPE significa "Floating Point Exception" (Excepción de Punto Flotante) y se genera cuando ocurren errores durante operaciones aritméticas, como la división por cero, el desbordamiento de enteros o el intento de calcular funciones matemáticas indefinidas, como el logaritmo de un número negativo.

Esta señal, SIGFPE se utiliza para notificar al programa que ha ocurrido un error durante una operación aritmética, lo que permite al programa manejar adecuadamente la situación, como terminar la ejecución o manejar el error de otra manera. Es importante gestionar estas excepciones para evitar comportamientos inesperados o fallos en el programa.

Veremos como tratar las excepciones, no las interrupciones.

5.1. Sintaxis





Entre el bloque try {} y el catch{} NO VAN SENTENCIAS.

Try

C++ proporciona bloques try para permitir el manejo de excepciones.
Un bloque try consiste en la palabra clave try, seguida de llaves ({}) que definen un bloque de código en el que podrían ocurrir errores. El bloque try encierra instrucciones que podrían ocasionar excepciones, e instrucciones que se deberían omitir si ocurre una excepción.
El control del programa no regresa al punto en el que ocurrió la excepción (conocido como el punto de lanzamiento), debido a que el bloque try ha expirado.
El bloque try SOLO completa su ejecución con éxito si no aparecen excepciones, si hay excepciones se interrumpe.


Catch

Las excepciones se procesan mediante los manejadores catch (también conocidos como manejadores de excepciones), que atrapan y manejan las excepciones.
Por lo menos debe haber un manejador catch inmediatamente después de cada bloque try.

Catch al igual que una función puede recibir distintos argumentos ( etiquetas de excepción ) separados por coma.

Cada manejador catch empieza con la palabra clave catch y especifica entre paréntesis un parámetro de excepción que representa el tipo de excepción que puede procesar el manejador catch. (=> pueden existir varios catch).

Cuando ocurre una excepción en un bloque try, el manejador catch que se ejecuta es aquél cuyo tipo de dato coincide con el tipo de dato de la excepción que ocurrió.

Puesto que los bloques catch se procesan por orden de programa para encontrar un tipo coincidente, un catch(...)  de puntos suspensivos debe ser el último controlador del bloque try asociado. Use catch(...) con precaución; no permita que un programa continúe a menos que el bloque catch sepa cómo controlar la excepción específica detectada. Normalmente, un bloque catch(...) se emplea para registrar errores y realizar limpiezas especiales antes de que se detenga la ejecución de un programa.

catch(...) captura cualquier cosa que haya sido lanzada con throw, ver que aquí NO hay argumento. Dado que en c++ puedes lanzar cualquier cosa, no habría manera de saber qué variable poner ahí, en general no sería bueno usar catch(...), ya que indicaría que es una captura genérica, por lo que no sabemos el por que de la excepción.

Por lo general, un manejador catch reporta el error al usuario, lo registra en un archivo, termina el programa sin que haya pérdida de datos o intenta una estrategia alterna para realizar la tarea fallida, por ejemplo pedir un reingreso.

Si ocurre una excepción como resultado de una instrucción en un bloque try, este bloque expira (es decir, termina de inmediato). A continuación, el programa busca el primer manejador catch que pueda procesar el tipo de excepción que ocurrió. El programa localiza el catch que coincida, comparando el tipo de la excepción lanzada con el tipo del parámetro de excepción de cada catch, hasta que el programa encuentra una coincidencia.

Regla relacionada con try/catch:
Si NO escribimos una sección de captura que corresponda a determinado tipo de excepción, esta excepción no se capturará en el caso de que se produzca, y seguirá su destructivo trayecto por la pila de ejecución de la aplicación, por este motivo se podría usar catch(...).

Throw

La instrucción throw, que significa “lanzar”. Esta instrucción interrumpe la ejecución del programa y provoca la terminación de todas las funciones pendientes, a menos que se tomen medidas especiales.
Al ejecutar la excepción hay que asociarle un valor que porte la información acerca de la situación que la ha provocado.
Un ejemplo típico de instrucción para elevar una excepción es el siguiente:

throw atencion("¡Algo va mal!");

En esta línea el comando throw crea una clase llamada "atención" en tiempo de ejecución, con el argumento de constructor; "¡Algo va mal!". El mensaje se almacena en la propiedad Message del objeto de excepción construido.

Hay un detalle importante: la ejecución de throw va interrumpiendo una a una cada función pendiente en la pila de la aplicación y va destruyendo las variables locales.

El código después de la cláusula try es la sección de código protegida, la expresión throw inicia (es decir, produce) una excepción, así que throw es la que lanza una excepción.

El operando de una instrucción throw puede ser de cualquier tipo. Si el operando es un objeto (un objeto es la instancia de una clase ) lo llamamos objeto excepción; pero también puede ser un operando, como el valor de una expresión ( por ejemplo, throw x > 0) o el valor de un int ( por ejemplo, throw -1).

Captura todas las excepciones en C++

El nuevo mecanismo de excepción actual es una forma de capturar todas las excepciones en C++; se introdujo en C++11.  Es una alternativa a la técnica antigua de capturar solo algunas excepciones con bloques try-catch.

Forma antigua:

La forma antigua era usar la palabra clave throw para lanzar una excepción y atraparla con un bloque try-catch

Forma nueva:

La nueva forma es usar la palabra clave throw seguida de una lista de tipos de excepciones que queremos capturar.

Nota del Docente:
Para los casos sencillos que veamos, es probable que no se perciban o existan las diferencias entre la forma nueva y la vieja.

Pasos para capturar todas las excepciones en C++

Se necesitan los siguientes pasos para detectar todas las excepciones en C++:
  1. Declare una clase que se usará como controlador de excepciones.
  2. Defina qué excepciones debe capturar este controlador.
  3. Haga que la función principal llame al nuevo mecanismo de excepción de C++ 11 con una instancia de la clase utilizada para detectar excepciones.
  4. Escriba el código que puede generar una excepción y asegúrese de que el mecanismo de excepción actual lo atrape.
El nuevo mecanismo de excepción de C++11 facilita que los programadores se aseguren de detectar todos los posibles errores de tiempo de ejecución en su código sin tener que escribir manualmente bloques try-catch para cada uno de ellos.

Error del tipo Segmentatio fault:

Segmentation fault NO es una excepción !!.Los errores como las fallas de segmentación son de nivel inferior y try-catch ignora estos eventos y se comporta igual que si no hubiera un bloque try-catch. Se define como violación de acceso (violación del segmento o access violation y segmentation fault en Inglés) al intento fallido de acceso a información o a programas a los que no se tiene autorización para ver o modificar

https://www.delftstack.com/es/howto/cpp/cpp-catch-all-exceptions/#escriba-el-c%C3%B3digo-que-puede-generar-una-excepci%C3%B3n-y-aseg%C3%BArese-de-que-el-mecanismo-de-excepci%C3%B3n-actual-lo-atrape

5.2. Destrucción de Objetos Dinámicos.

El gran problema de la destrucción de objetos temporales consiste en que no todos ellos se crean directamente como variables de pila, sino que muchas veces se utilizan punteros a objetos.

Archivo terminos_fibonacci.txt:


Código:

Las reglas de C++ son claras al respecto: los objetos creados en la memoria dinámica deben ser destruidos explícitamente mediante una llamada al operador delete. ¿Y si ocurre una excepción dentro de suma?, no se libera el objeto, se pierde su memoria y se queda un fichero abierto.

Si esto fuera al acceso a una tabla de una base de datos al perder conectividad con el Servidor:

  • la misma podría quedar con inconsistencias
  • o al acceder está bloqueada por que otro proceso la está usando,  por ejemplo.

Podemos ver que en algunos casos no tenemos manera de anticiparnos a una posible excepción!!



5.3. Tipos de Excepciones.

  1. Excepciones incorporadas o predefinidas

  2. Excepciones creadas personalizadas.

Excepciones incorporadas o predefinidas

La Biblioteca Estándar de C++ proporciona un conjunto de excepciones incorporadas derivadas de la clase std::exception, como std::runtime_error, std::out_of_range, etc.

Estas excepciones incorporadas tienen una jerarquía:


Estas excepciones incorporadas se pueden utilizar para atrapar tipos específicos de errores. Además, también puedes definir tus propias excepciones personalizadas. https://en.cppreference.com/w/cpp/error/exception

  

Hay excepciones estándar:


y se pueden crear otras excepciones con clases, también veremos un ejemplo de eso.

Excepciones creadas personalizadas.

Las excepciones personalizadas no especificadas en C++ pueden ser beneficiosas para producir bajo circunstancias específicas. En C++, cualquier tipo que cumpla con ciertos criterios puede ser capturado o arrojado.

Estos incluyen el tipo que tiene un constructor y un destructor de copias válidos.

Las excepciones personalizadas ofrecen el mecanismo de manejo de excepciones con información útil sobre un problema. Pueden crearse creando una nueva clase con las propiedades necesarias y lanzando una instancia de esa clase o heredando de std::exception y anulando la función what().

Podemos hacer una clase de excepción personalizada de la siguiente manera.


Class MyExceptionClass : public exception {
 public:
  const char* what() const throw() { return "Negative number not allowed\n"; }
};
Esto se puede capturar en el programa del controlador de la siguiente manera.

int main() {
  try {
    int a;
    cout << "Enter a number : \n";
    cin >> a;
    if (a < 0) {
      MyExceptionClass c;
      throw c;
    } else {
      cout << "you entered: " << a << endl;
    }
  } catch (exception& e) {
    cout << e.what();
  }
}
Podemos ver en el código anterior que hemos heredado nuestra clase de excepción personalizada con la clase Exception y anulamos un método what() que se llamará cuando se produzca la excepción.

5.4. División por cero



En este caso, la línea 15 verifica que b=0, si esto es cierto, el if "tira" (trhow) un -1.

Esto es atrapado por catch, que toma como argumento el -1, por lo que x=-1, y lo muestra junto con el mensaje "No se puede..." por pantalla. Vamos como sería la salida:

Veamos otro ejemplo, en este una función es la va a intentar realizar el cociente, y si no puede por que el denominador es cero, lanzará una excepción.

Observemos que en este caso, el throw, NO se encuentra ubicado directamente en el bloque del try, pero sí indirectamente, ya que el bloque try, invoca a la función "división"  y esta si tiene el throw.

Podemos ver que en la línea 6: El string que sigue a throw es el argumento que se pasa a catch, este es coincidente con el argumento que recibe catch en la línea 20. Se muestra en pantalla el argumento que recibe, renombrado como variable local msj. Se muestra que cout y cerr, ambos flujos son redireccionados para la pantalla.


5.5. Fuera de rango

Veamos un ejemplo, donde tratamos de acceder a un caracter de un string que no existe, el string tiene 5 caracteres y quiero mostrar el decimo.

En este ejemplo usamos la librería string.

Los errores generados por las librerías estándar de C++ pueden ser capturados por un catch ( ver que no hay throw visible)  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. En particular este ejemplo trata de acceder a algo fuera de rango, por lo que causa un error de rango.

Aqui podemos acceder a la documentación de referencia: https://en.cppreference.com/w/cpp/error/range_error

La clase exception está incluida en la libreria estandar exception y su estructura es la siguiente:

class exception {
public:
    exception() throw() { }
    virtual ~exception() throw();
    virtual const char* what() const throw();
};
El método what() en la clase exception, es el encargado de generar el mensaje que trata de explicar la naturaleza del error ocurrido.

Si en nuestro ejemplo se nos ocurre mostrar un caracter de txt que esté más allá que la 4 posición, va a dispara una exception.



5.6. Excepciones específicas.

Vamos a mostrar un ejemplo donde vamos a crear varias excepciones para capturar distintas excepciones en el cálculo de un logaritmo.

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.


5.7. Argumento no válido.

Vamos a mostrar un ejemplo de código, en el que se llama a una excepción cuando el argumento que recibe una función se considera que no es válido y se debe lanzar la excepción.


Veamos que sucede, si se ingresa un para de valores positivos, en ese caso la excepción no se dispara.


Veamos ahora el caso de que se ingrese un valor negativo, esto dispara la excepción , línea 8 throw.



La líena 8 llanza la excepción y el catch de la línea 24 se ejecuta.

5.8. Error en tipo de datos.

Veamos como solucionar el tipo de error en datos. 

En Python esto se acostumbraba hacer con una excepción, no es así en C++.

Vemos que no es necesario levantar una excepción para el caso de error en tipo de datos. Si ingresamos la letra o caracter "A":


El orden de cin.clear() y cin.ignore() no es importante, pero deben estar ambos.

  • cin.ignore(): deja de extraer caracteres si se alcanza el final del archivo
  • cin.clear(): limpia la bandera de error de cin.

De esta manera no se genera un loop.

Este código tendría que ser mejorado, que pasa si se ingresa mas que un caracter , por ejemplo "hola"?


Podemos ver que con cada uno de los 4 caracteres de hola , muestra: "Ud. NO ingresó..."

Para ello podemos modificar el código de la siguiente manera:

Si usamos algo como #define max_size 50 y luego cin.ignore(max_size) luego deberemos llegar a tipera 50 caracteres hasta que ignore deje de realizar su tarea.

Así que vamos a usar cin.getline() para leer los caracteres que quedan en el buffer, el código quedaría algo como:



Si ahora ejecutamos y escribimos hola, se vería:


Este ejemplo le falta algo para ser perfecto, pero se deja al alumno. Probar ingresar 2,34 en lugar de 2.34 y ver que pasa.

5.9. No hay espacio de Memoria (new)

Cuando solicitamos memoria al sistema, lo habitual es que exista suficiente memoria disponible y no haya ningún problema. Pero si queremos realizar una aplicación robusta deberemos de tener en cuenta la eventualidad de que dicha memoria no se conceda.



La excepción que puede detectar el controlador de excepciones en este ejemplo es bad_alloc. Debido a que bad_alloc se deriva de la clase exception,  se puede capturar (capturar por referencia, capturar todas las clases relacionadas).
Como el tamaño solicitado es muy grande, no hay lugar.