viernes

3. Manejo de Excepciones Y Clase String.

INVESTIGACION

CAPITULO 16 "MANEJO DE EXCEPCIONES"

16.1. INTRODUCCION.

Una excepción es la indicación de un problema que ocurre durante la ejecución de un programa. El nombre “excepción” implica que el problema ocurre con poca frecuencia; si la “regla” es que una instrucción se ejecuta en forma correcta, entonces la “excepción a la regla” es cuando ocurre un problema. El manejo de una excepción permite que un programa continúe su ejecución como si no se hubiera encontrado un problema. Un problema más grave podría evitar que un programa continuara su ejecución normal, en vez de requerir al programa que notifique al usuario sobre el problema antes de terminar de una manera controlada.

16.2. GENERALIDADES ACERCA DEL MANEJO DE EXCEPCIONES.

Con frecuencia, los programas evalúan condiciones que determinan cómo debe proceder la ejecución del programa. Considere el siguiente seudocódigo:
*      Realizar una tarea
*      Si la tarea anterior no se ejecutó correctamente
*      Realizar el procesamiento de los errores
*      Realizar la siguiente tarea
*      Si la tarea anterior no se ejecutó correctamente 
*      Realizar el procesamiento de los errores

En este seudocódigo, empezaremos por realizar una tarea; después evaluaremos si se ejecutó en forma correcta. Si no lo hizo, realizamos el procesamiento de los errores. De otra manera, continuamos con la siguiente tarea.
El manejo de excepciones permite al programador remover el código para manejo de errores de la “línea principal” de ejecución del programa, lo cual mejora la claridad y capacidad de modificación del mismo. Esta flexibilidad reduce la probabilidad de que los errores se pasen por alto y, por consecuencia, hace que los programas sean más robustos.

16.3. EJEMPLO: MANEJO DE UN INTENTO DE DIVIDIR ENTRE CERO.

El propósito de este ejemplo es mostrar cómo evitar un problema aritmético común: la división entre cero. En C++, la división entre cero mediante el uso de aritmética de enteros por lo general hace que un programa termine en forma prematura. En la aritmética de punto flotante, ciertas implementaciones de C++ permiten la división entre cero, en cuyo caso el resultado de infinito positivo o negativo se muestra como INF o –INF.
En este ejemplo definimos una función llamada cociente, la cual recibe dos enteros introducidos por el usuario, y divide su primer parámetro int entre su segundo parámetro int. Antes de realizar la división, la función convierte el valor del primer parámetro entero al tipo double. Después, el valor del segundo parámetro int se promueve al tipo double para el cálculo. Así, la función cociente en realidad realiza la división utilizando dos valores double y devuelve un resultado double.  Aunque la división entre cero está permitida en la aritmética de punto flotante, para el propósito de este ejemplo trataremos un intento de división entre cero como un error. Así, la función cociente evalúa su segundo parámetro para asegurar que no sea cero antes de permitir que continúe la división. Si el segundo parámetro es cero, la función utiliza una excepción para indicar a la función que hizo la llamada que ocurrió un problema. Así, la función que hizo la llamada puede procesar la excepción y permitir que el usuario escriba dos nuevos valores, antes de llamar a la función cociente de nuevo. De esta forma, el programa puede seguir ejecutándose aun después de que se introduzca un valor inapropiado, con lo cual el programa se vuelve más robusto.
El ejemplo consiste en dos archivos. ExcepcionDivisionEntreCero.h define una clase de excepción que representa el tipo del problema que podría ocurrir en el ejemplo, define la función cociente y la función main que llama a la clase de excepción. La función main contiene el código que demuestra el manejo de excepciones.

Definición de una clase de excepción para representar el tipo del problema que podría ocurrir
Se define la clase ExcepcionDivisionEntreCero como una clase derivada de la clase runtime_ error (definida en el archivo de encabezado <stdexcept>).
La clase runtime_error, una clase derivada de la clase exception de la Biblioteca estándar (definida en el archivo de encabezado <exception>), es la clase base estándar de C++ para representar errores en tiempo de ejecución. La clase exception es la clase base estándar de C++ para todas las excepciones.
Una clase de excepción típica que se deriva de la clase runtime_error define sólo un constructor que pasa una cadena de mensaje de error al constructor de la clase base runtime_error. Ejemplo:
*      ExcepcionDivisionEntreCero()
*      Runtime_error( “intento de dividir entre cero” ) {}
Cada clase de excepción que se deriva en forma directa o indirecta de exception contiene la función virtual what, la cual devuelve el mensaje de error de un objeto excepción. No es obligatorio derivar una clase de excepción personalizada, esto permite a los programadores utilizar la función virtual what para obtener un mensaje de error apropiado.

Demostración del manejo de excepciones.
Se utiliza el manejo de excepciones para envolver código que podría lanzar una excepción de “división entre cero” y para manejar esa excepción, en caso de que ocurra una. La aplicación permite al usuario introducir dos enteros, que se pasan como argumentos a la función cociente. Esta función divide su primer parámetro (numerador) entre su segundo parámetro (denominador). Suponiendo que el usuario no especifica 0 como el denominador para la división, la función cociente devuelve el resultado de la división. Sin embargo, si el usuario introduce un 0 para el denominador; la función cociente lanza una excepción. Cuando ocurre la excepción, el programa informa al usuario del error y le pide que introduzca dos nuevos enteros.

Encerrar código en un bloque try
El programa empieza pidiendo al usuario que introduzca dos enteros. Estos enteros se introducen en la condición del ciclo while.
*      while(cin >> numero1 >>numero2)
El manejo de excepciones está orientado a situaciones en las que la función que detecta un error no puede manejarlo.  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.
1.      try{
2.      resultado = cociente( numero1, numero2 );
3.      cout << “El cociente es: ” << resultado << endl;
4.      }

Definición de un manejador catch para procesar una ExcepcionDivisionEntreCero
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. 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 (en este caso, ExcepcionDivisionEntreCero).
Cuando ocurre una excepción en un bloque try, el manejador catch que se ejecuta es aquél cuyo tipo coincide con el tipo de la excepción que ocurrió (es decir, el tipo en el bloque catch coincide exactamente con el tipo de excepción lanzada, o es una clase base de la misma).
Si un parámetro de excepción incluye un nombre de parámetro opcional, el manejador catch puede usar ese nombre de parámetro para interactuar con la excepción atrapada en el cuerpo del manejador catch, que está delimitado por llaves ({ y }). 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.
1.      catch( ExcepcionDivisionEntreCero &divideByZeroException){
2.      cout << “Ocurrio una excepción: ” << divideByZeroException.what() << endl;
3.      }

Modelo de terminación del manejo de excepciones
Si ocurre una excepción como resultado de una instrucción en un bloque try, este bloque expira (es decir, termina de inmediato).
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. Ocurre una coincidencia si los tipos son idénticos, o si el tipo de la excepción lanzada es una clase derivada del tipo del parámetro de excepción. Cuando ocurre una coincidencia, se ejecuta el código contenido en el manejador catch que coincide.
Cuando un manejador catch termina su procesamiento al llegar a su llave derecha de cierre (}), se considera que la excepción se manejó y las variables locales definidas dentro del manejador catch quedan fuera de alcance. El control del programa no regresa al punto en el que ocurrió la excepción debido a que el bloque try ha expirado. A esto se le conoce como modelo de terminación del manejo de excepciones.
Si el bloque try completa su ejecución con éxito, entonces el programa ignora los manejadores catch y el control del programa continúa con la primera instrucción después del último bloque catch que sigue de ese bloque try. Si no ocurren excepciones en un bloque try, el programa ignora el (los) manejador(es) catch para ese bloque. Si una excepción que ocurre en un bloque try no tiene un manejador catch que coincida, o si una excepción ocurre en una instrucción que no se encuentre dentro de un bloque try, la función que contiene la instrucción termina de inmediato y el programa intenta localizar un bloque try circundante en la función que hizo la llamada. A este proceso se le conoce como limpieza de la pila.

Flujo del control del programa cuando el usuario introduce un denominador distinto de cero.
Considere el flujo de control cuando el usuario introduce el numerador 100 y el denominador 7. En la línea siguiente, la función cociente determina que el denominador no es igual a cero:
*      if( denominador == 0 )
Por lo que acontinuación en la línea se realiza la división y se devuelve el resultado (14.2857):
*      return static_cast< double > ( numerador ) / denominador;
Como el bloque try se completó con éxito y no lanzó una excepción, el programa no ejecuta las instrucciones contenidas en el manejador catch, y el control continúa a la próxima línea en donde se pide al usuario que introduzca dos enteros más:
*      cout << “\nEscriba dos enteros (fin de archivo para terminar ): “;

Flujo del control del programa cuando el usuario escribe un denominador de cero.
Ahora vamos a considerar un caso más interesante, en el que el usuario introduce el numerador 100 y el denominador 0. En las siguientes líneas de código, cociente determina que el denominador es igual a cero, lo cual indica un intento de división entre cero. Se lanza una excepción, que representamos como un objeto de la clase ExcepcionDivisionEntreCero. Para lanzar una excepción, Se utiliza la palabra clave throw seguida de un operando que representa el tipo de excepción a lanzar. Por lo general, una instrucción throw especifica un operando.
1.      if(  denominador == 0){
2.      throw ExcepciondivisionEntreCero();
El operando de una instrucción throw puede ser de cualquier tipo. Si el operando es un objeto, lo llamamos objeto excepción. Sin embargo, un operando de throw también puede asumir otros valores, como el valor de una expresión que no produce un objeto (por ejemplo, throw x > 5) o el valor de un int ( por ejemplo, throw 5).
Como parte de lanzar una excepción, se crea el operando throw y se utiliza para inicializar el parámetro en el manejador catch. La instrucción throw crea un objeto de la clase ExcepcionDivisionEntreCero. Esta es una característica central del manejo de excepciones: una función debe lanzar una excepción antes de que el error tenga la oportunidad de manifestarse. En general, cuando se lanza una excepción dentro de un bloque try, la excepción se atrapa mediante un manejador catch que especifica el tipo que coincide con la excepción lanzada. El manejador catch especifica que atrapa objetos ExcepcionDivisionEntreCero; este tipo coincide con el tipo del objeto lanzado en la función cociente.

16.4 CUANDO UTILIZAR EL MANEJO DE EXCEPCIONES.
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 y la asignación fallida de memoria (debido a la falta de memoria). El manejo de excepciones no está diseñado para procesar los errores asociados con los eventos síncronos (por ejemplo, completar la E/S de disco, la llegada de mensajes de red, los clics del ratón y las pulsaciones de tecla), los cuales ocurren en paralelo con, y de manera independiente a, el flujo de control del programa.
El mecanismo de manejo de errores también es útil para procesar problemas que ocurren cuando un programa interactúa con los elementos de software, como las funciones miembro, los constructores, los destructores y las clases. En vez de manejar los problemas de manera interna, dichos elementos de software utilizan comúnmente las excepciones para notificar a los programas cuando ocurren problemas.
Por lo general, las aplicaciones complejas consisten en componentes predefinidos de software y componentes específicos de cada aplicación que utilizan los componentes predefinidos. Cuando un componente predefinido encuentra un problema, ese componente necesita un mecanismo para comunicar el problema al componente específico de la aplicación; el componente predefinido no puede saber de antemano cómo procesa cada aplicación un problema que se presenta.

16.5. VOLVER A LANZAR UNA EXCEPCION.
Es posible que un manejador de excepciones, al momento de recibir una excepción, decida que no puede procesar esa excepción o que puede procesar la excepción sólo de manera parcial. En tales casos, el manejador de excepciones puede diferir el manejo de excepciones a otro manejador de excepciones. En cualquier caso, para lograr esto hay que volver a lanzar la excepción mediante la siguiente instrucción:
*      throw;
Sin importar el que un manejador pueda o no procesar una excepción, el manejador puede volver a lanzar la excepción para seguirla procesando fuera de éste.

16.6. ESPECIFICACIONES DE EXCEPCIONES.
Una especificación de excepciones opcional (también conocida como lista throw) enumera una lista de excepciones que puede lanzar una función. Por ejemplo, considere la siguiente declaración de una función:
1.      int unaFuncion( double valor ) 
2.      throw ( ExcepcionA, ExcepcionB, ExcepcionC )
3.     
4.      // cuerpo de la función
5.      }
En esta definición, la especificación de la función, que empieza con la palabra clave throw justo después del paréntesis de cierre de la lista de parámetros de la función, indica que la función unaFuncion puede lanzar excepciones de los tipos ExcepcionA, ExcepcionB y ExcepcionC. Una función sólo puede lanzar excepciones de los tipos indicados por la especificación, o excepciones de cualquier tipo derivado de estos tipos. Si la función lanza (throw) una excepción que no pertenezca a un tipo especificado, el mecanismo de manejo de excepciones llama a la función unexpected, la cual termina el programa. Una función que no proporciona una especificación de excepciones puede lanzar cualquier excepción. Al colocar throw() (una especificación de excepciones vacía) después de la lista de parámetros de una función, se establece que esa función no lanza excepciones. Si la función intenta lanzar una excepción, se invoca la función unexpected.

16.7. PROCEDIMIENTO DE EXCEPCIONES INESPERADAS.
La función unexpected llama a la función registrada con la función set_unexpected (definida en el archivo de encabezado <exception>). Si no se ha registrado una función de esta forma, se hace una llamada a la función terminate de manera predeterminada. Los casos en los que se hace una llamada a la función terminate son:
1.      El mecanismo de excepción no puede encontrar un catch que coincida para una excepción que se haya lanzado.
2.      Un destructor intenta lanzar una excepción durante la limpieza de la pila.
3.      Hay un intento de volver a lanzar una excepción cuando no se esté manejando una excepción en un momento dado.
4.      Una llamada a la función unexpected realiza una llamada a la función terminate de manera predeterminada.
La función set_terminate puede especificar la función a invocar cuando se haga la llamada a terminate. En caso contrario, terminate llama a abort, con lo cual se termina el programa sin llamar a los destructores de cualquier objeto restante de una clase de almacenamiento estático o automático.
Esto podría conllevar a fugas de recursos cuando un programa termina en forma prematura. Las funciones set_terminate y set_unexpected devuelven un apuntador a la última función llamada por terminate y unexpected, respectivamente. Las funciones set_terminate y set_unexpected reciben como argumentos apuntadores a funciones con tipos de valores de retorno void y sin argumentos. Si la última acción de una función de terminación definida por el programador no es salir de un programa, se hará una llamada a la función abort para terminar la ejecución del programa una vez que se ejecuten las demás instrucciones de la función de terminación definida por el programador.

16.8. LIMPIEZA DE LA PILA.
Cuando se lanza una excepción pero no se atrapa en un alcance específico, la pila de llamadas a funciones se “limpia” y se hace un intento de atrapar (catch) la excepción en el siguiente bloque try...catch exterior.
Limpiar la pila de llamadas a funciones significa que la función en la que no se atrapó la excepción termina, todas las variables locales en la función se destruyen y el control regresa a la instrucción que invocó originalmente a esa función. Si un bloque try encierra esa instrucción, se hace un intento de atrapar la excepción. Si un bloque try no encierra esa instrucción, se vuelve a realizar la limpieza de la pila. Si no hay un manejador catch que atrape a esta excepción, se hace una llamada a la función terminate para terminar el programa. Recuerde que la función what es una función virtual de la clase exception que una clase derivada puede sobrescribir para que devuelva un mensaje de error apropiado.

 16.9 CONSTRUCTORES, DESTRUCTORES Y MANEJO DE EXCEPCIONES.
El constructor de un objeto responde cuando new falla debido a que no pudo asignar la memoria requerida para almacenar la representación interna de ese objeto. Como el constructor no puede devolver un valor para indicar un error, debemos elegir un medio alternativo de indicar que el objeto no se ha construido en forma apropiada. Un esquema es establecer una variable fuera del constructor. La alternativa preferida es requerir que el constructor lance una excepción que contenga la información del error, con lo cual se ofrece una oportunidad para que el programa maneje la falla.
Antes de que un constructor lance una excepción, se hacen llamadas a los destructores para cualquier objeto miembro que se construya como parte del objeto que se va a construir. Se hacen llamadas a los destructores para todos los objetos automáticos construidos en un bloque try antes de lanzar una excepción. Se garantiza que la limpieza de la pila se habrá completado en el momento en el que un manejador de excepciones se empiece a ejecutar.
Si un destructor invocado como resultado de la limpieza de la pila lanza una excepción, se hace una llamada a terminate. Si un objeto tiene objetos miembro, y si se lanza una excepción antes de que el objeto exterior se construya por completo, entonces se ejecutarán los destructores para los objetos miembro que se hayan construido antes de la ocurrencia de la excepción. Si se ha construido parcialmente un arreglo de objetos cuando ocurre una excepción, sólo se llamará a los destructores para los objetos construidos en el arreglo. 
Una excepción podría impedir la operación de código que por lo general liberaría un recurso, con lo cual se produciría una fuga de recursos. Una técnica para resolver este problema es inicializar un objeto local para adquirir el recurso. Cuando ocurra una excepción, se invocará al destructor para ese objeto y se podrá liberar el recurso.

16.10. EXCEPCIONES Y HERENCIA.
Se pueden derivar varias clases de excepciones de una clase base común, cuando creamos la clase ExcepcionDivisionEntreCero como una clase derivada de la clase exception. Si un manejador catch maneja un apuntador o referencia a un objeto excepción del tipo de una clase base, también puede atrapar un apuntador o referencia a todos los objetos de las clases que se deriven públicamente de esa clase base; esto permite el procesamiento polimórfico de los errores relacionados.

16.11. PROCEDIMIENTO DE LAS FALLAS DE NEW.
El estándar de C++ especifica que, cuando falla el operador new, lanza una excepción bad_alloc (definida en el archivo de encabezado <new>).
Caso en el que new devuelve 0 al fallar
La figura 16.5 demuestra cómo new devuelve 0 al fallar en asignar la cantidad requerida de memoria. La instrucción for en las líneas 13 a 24 debería iterar 50 veces y, en cada pasada, asignar un arreglo de 50,000,000 valores double (es decir, 400,000,000 bytes, ya que un double es por lo general de 8 bytes). La instrucción if en la línea 17 evalúa el resultado de cada operación de new para determinar si new asignó la memoria con éxito. Si new falla y devuelve 0, en la línea 19 se imprime un mensaje de error y el ciclo termina. Los resultados muestran que el programa sólo realizó tres iteraciones antes de que new fallara, y el ciclo terminó. Los resultados del lector podrían diferir con base en la memoria física, el espacio en disco disponible para la memoria virtual en su sistema y el compilador que esté utilizando.

Caso en el que new lanza bad_alloc al fallar.
1.      #include <iostream>
2.      #include <new>
3.      using namespace std;
4.      using std::bac_alloc;
5.      int main{
6.      doublé *ptr[ 50 ];
7.      try{
8.      for ( int i = 0; i < 50; i++){
9.      ptr[ i ] = new double[ 50000000 ];
10.  cout << “ptr[“ << i << “] apunta a 50,000,000 nuevos valores double\n”;
11.  }
12.  catch( bad_alloc &ExcepcionAsignacionMemoria ){
13.  cerr << “Ocurrio una Excepcion: ” << ExcepcionAsignacionMemoria.what(); << endl;
14.  }
15.  }
En el código anterior demuestra cómo new lanza bad_alloc al fallar en asignar la memoria solicitada. La instrucción for, dentro del bloque try debe iterar 50 veces y en cada pasada asigna un arreglo de 50,000,000 valores double. Si new falla y lanza una excepción bad_alloc, el ciclo termina y el programa continúa en la línea 12, en donde el manejador catch atrapa y procesa la excepción. En las líneas 12 y 13 se imprime el mensaje "Ocurrio una excepción:" seguido del mensaje devuelto de la versión de la función what correspondiente a la clase base exception.
Los resultados muestran que el programa sólo realizó tres iteraciones del ciclo antes de que fallara new y se lanzara la excepción bad_alloc. Los resultados del lector podrían diferir con base en la memoria física, espacio en disco disponible para la memoria virtual en su sistema, y el compilador que esté utilizando.
Para este propósito, el archivo de encabezado <new> define el objeto nothrow (de tipo nothrow_t), que se utiliza de la siguiente manera:
*      double *ptr = new( nothrow ) double[ 50000000 ];
La instrucción anterior utiliza la versión de new que no lanza excepciones bad_alloc (es decir, nothrow) para asignar un arreglo de 50,000,000 valores double.

Manejo de las fallas de new mediante la función set_new_handler
Una característica adicional para manejar las fallas de new es la función set_new_handler (cuyo prototipo se encuentra en el archivo de encabezado estándar <new>). Esta función recibe como argumento un apuntador a una función que no recibe argumentos y devuelve void. Este apuntador apunta a la función que se llamará si new falla. Esto proporciona al programador una metodología uniforme para manejar todas las fallas de new, sin importar que ocurra una falla en el programa. Una vez que set_new_handler registra un manejador de new en el programa, el operador new no lanza bad_alloc en el futuro; en vez de ello, difiere el manejo de errores a la función manejadora de new. Si new asigna memoria con éxito, devuelve un apuntador a esa memoria. Si new falla en asignar memoria y set_new_handler no registró una función manejadora de new, lanza una excepción bad_alloc. Si new falla al asignar memoria y se ha registrado una función manejadora de new, se hace una llamada a esta función. El estándar de C++ especifica que la función manejadora de new debe realizar una de las siguientes tareas:
1.      Hacer más memoria disponible al eliminar otra parte de la memoria asignada en forma dinámica (o indicando al usuario que cierre otras aplicaciones) y regresar al operador new para tratar de asignar memoria otra vez.
2.      Lanzar una excepción de tipo bad_alloc.
3.      Llamar a la función abort o exit (ambas se encuentran en el archivo de encabezado <cstdlib>) para terminar el programa.
En el siguiente código se demuestra el uso de set_new_handler. La función nuevoManejadorPersonalizado imprime un mensaje de error (línea 9) y después termina el programa mediante una llamada a abort (línea 10). Los resultados muestran que el programa sólo realizó tres iteraciones del ciclo, debido a que new falló e invocó a la función nuevoManejadorPersonalizado. Los resultados del lector podrían diferir con base en la memoria física, el espacio disponible para la memoria virtual en su sistema y el compilador que utilice para compilar el programa.
1.      #include <iostream>
2.      #include <new>
3.      #include <cstdlib>
4.      using::std cerr;
5.      using::std cout;
6.      using::std set_new_handler;
7.      using::std abort;
8.      void nuevoMAnejoPerzonalizado(){
9.      cerr << “Se llamo a nuevoManejoPersonalizado”;
10.  abort();
11.  }
12.  Int main(){
13.  double *ptr[ 50 ];
14.  set_new_handler( nuevoManejoPersonalizado);
15.  for( int i = 0; i < 50; i++){
16.  ptr[ i ] = new double[ 50000000 ];
17.  cout << “ptr[“ << i << “] apunta a 50,000,000 nuevos valores\n”;
18.  }
19.  return 0;
20.  }

16.12. LA CLASE auto_ptr Y LA ASIGNACIÓN DINÁMICA DE MEMORIA.
Una práctica común de programación es asignar la memoria dinámica, asignar la dirección de la memoria a un apuntador, usar el apuntador para manipular la memoria y desasignar la memoria con delete cuando ésta ya no sea necesaria. Si ocurre una excepción después de una asignación exitosa de memoria, pero antes de que se ejecute la instrucción delete, podría ocurrir una fuga de memoria.
El estándar de C++ proporciona la plantilla de clase auto_ptr en el archivo de encabezado <memory> para lidiar con esta situación. Un objeto de la clase auto_ptr mantiene un apuntador a la memoria que se asigna en forma dinámica. Cuando se hace una llamada al destructor de un objeto auto_ptr, realiza una operación delete con su miembro de datos apuntador. La plantilla de clase auto_ptr proporciona los operadores sobrecargados * y ->, de manera que un objeto auto_ptr se puede utilizar de la misma forma que una variable apuntador común.
1.      #include <iostream>
2.      #include <memory>
3.      #include “Entero.h”
4.      using::std cout;
5.      using::std endl;
6.      using:: std auto_ptr;
7.      int main() {
8.      cout << “Creacion de un objeto auto_ptr que apunta a un objeto Entero\n”;
9.      auto_ptr< Entero> ptrAEntero( new Entero( 7 ) );
10.  cout << “\nUso de auto_ptr para manipular el objeto Entero\n”;
11.  ptrAEntero -> setEntero( 99 );
12.  cout << “Entero después de setEntero: ” << ( *ptrAEntero).getEntero()
13.  return 0;
14.  }
En la línea 9 del código anterior se crea el objeto ptrAEntero de auto_ptr, y se inicializa con un apuntador a un objeto Entero asignado en forma dinámica, que contiene el valor 7. En la línea 11 se utiliza el operador -> sobrecargado de auto_ptr para invocar a la función setEntero en el objeto Entero que maneja ptrAEntero. En la línea 12 se utiliza el operador * sobrecargado de auto_ptr para desreferenciar a ptrAEntero, y después se utiliza el operador punto (.) para invocar a la función getEntero en el objeto Entero. Al igual que un apuntador regular, los operadores sobrecargados -> y * de un objeto auto_ptr se pueden utilizar para acceder al objeto al que apunta el objeto auto_ptr.
Como ptrAEntero es una variable automática local en main, se destruye cuando main termina. El destructor de auto_ptr obliga a que se lleve a cabo una operación delete del objeto Entero al que apunta ptrAEntero, que a su vez llama al destructor de la clase Entero. La memoria que ocupa Entero se libera, sin importar cómo sale el control del bloque. Sólo un objeto auto_ptr puede poseer un objeto asignado en forma dinámica en un momento dado, y el objeto no puede ser un arreglo.
Al usar su operador de asignación sobrecargado o su constructor de copia, un objeto auto_ptr puede transferir la propiedad de la memoria dinámica que maneja. El último objeto auto_ptr que mantiene el apuntador a la memoria dinámica eliminará la memoria. Esto hace de auto_ptr un mecanismo ideal para devolver memoria asignada en forma dinámica al código cliente. Cuando el objeto auto_ptr queda fuera de alcance en el código cliente, el destructor de auto_ptr elimina la memoria dinámica.

16.13 JERARQUÍA DE EXCEPCIONES DE LA BIBLIOTECA ESTÁNDAR.
La experiencia ha demostrado que las excepciones se pueden clasificar en varias categorías. Esta jerarquía está encabezada por:la clase base exception, la cual contiene la función virtual what, que las clases derivadas pueden sobrescribir para generar los mensajes de error apropiados. Las clases derivadas inmediatas de la clase base exception incluyen a runtime_error y logic_error, cada una de las cuales tiene varias clases derivadas. De exception también se derivan las excepciones lanzadas por los operadores de C++; por ejemplo, bad_alloc es lanzada por new, bad_cast es lanzada por dynamic_cast y bad_typeid es lanzada por typeid. Incluir a bad_exception en la lista throw de una función significa que, si ocurre una excepción inesperada, la función unexpected puede lanzar a bad_exception en vez de terminar la ejecución del programa o llamar a otra función especificada por set_unexpected.

La clase logic_error es la clase base de varias clases de excepciones estándar que indican errores en la lógica del programa. la clase invalid_argument indica que se pasó un argumento inválido a una función. La clase length_error indica que para ese objeto se utilizó una longitud mayor que el tamaño máximo permitido para el objeto que se está manipulando. La clase out_of_range indica que un valor, como un subíndice en un arreglo, excedió su rango permitido de valores.
La clase runtime_error, es la clase base de varias otras clases de excepciones estándar que indican errores en tiempo de ejecución. La clase overflow_error describe un error de desbordamiento aritmético y la clase underflow_error describe un error de subdesbordamiento (es decir, el resultado de una operación aritmética es menor que el número más pequeño que se puede almacenar en la computadora).

16.14. OTRAS TÉCNICAS PARA MANEJAR ERRORES.
En capítulos anteriores hemos visto varias formas de tratar con situaciones excepcionales. A continuación se sintetizan éstas y otras técnicas para manejar errores:
*      Ignorar la excepción. Si ocurre una excepción, el programa podría fallar como resultado de la excepción que no se atrapó. Esto es devastador para los productos de software comerciales y el software de misión crítica y propósito especial, pero para el software desarrollado para nuestros propios fines, es común ignorar muchos tipos de errores.
*      Abortar el programa. Sin duda, esto evita que un programa se ejecute hasta completarse y que produzca resultados incorrectos. Para muchos tipos de errores esta técnica es apropiada, en especial para los errores no fatales que permiten a un programa ejecutarse hasta completarse. Esta estrategia es inapropiada para las aplicaciones de misión crítica. Las cuestiones sobre los recursos también son de importancia aquí; si un programa obtiene un recurso, debe liberarlo antes de terminar.
*      Establecer indicadores de errores. El problema con esta metodología es que los programas tal vez no comprueben estos indicadores de errores en todos los puntos en los que los errores podrían resultar problemáticos. Otro problema es que el programa, después de procesar el problema, tal vez no borre los indicadores de errores.
*      Probar la condición de error, generar un mensaje de error y llamar a exit en <cstdlib> para pasar un código de error apropiado al entorno del programa.
*      Usar las funciones setjump y longjump. Estas funciones de la biblioteca <csetjump> permiten al programador especificar un salto inmediato, desde una llamada a una función con muchos niveles de anidamiento hasta un manejador de errores. Las funciones setjump y longjump son peligrosas, ya que limpian la pila sin llamar a los destructores para los objetos automáticos. Esto puede provocar problemas graves.


CAPITULO 18 “LA CLASE STRING Y EL PROCESAMIENTO DE FLUJOS DE CADENA”.


18.1. INTRODUCCION
La plantilla de clase basic_string  proporciona operaciones comunes de manipulación de cadenas, las herramientas de soporte se definen de la siguiente manera:
*      typedef basic_string<char> string;
Se proporciona una definición de typedef para el tipo wchar_t. El tipo wchar_t almacena caracteres para soportar otros conjuntos de caracteres, además se debe incluir el archivo de encabezado <string>.
*      Sintaxis constructora
*      string texto(“hola”);
*      string mes = “marzo”;

La clase string también proporciona un constructor predeterminado (que crea una cadena vacía) y un constructor de copia. Una cadena vacía es un objeto string que no contiene caracteres. El operador = en la declaración no es una asignación, sino una llamada implícita al constructor de la clase string, que realiza la conversión. En la clase string no se proporcionan conversiones de int o char 2 string en una definición string.
Los objetos string no necesariamente tienen terminación nula. La longitud de un objeto string se puede obtener mediante la función miembro length y mediante la función size. El operador subíndice ([]), se utiliza con objetos string para acceder a caracteres individuales y modificarlos; los objetos string tiene un primer subíndice 0 y un último subíndice de lenght() -1. El operador de extracción de flujo (>>) se sobre carga para soportar objetos string.
*      string objetos1;
*      cin >> objetos1;
Declaran un objeto string y leen un objeto string. La función getline también se sobrecarga para objetos string. Asumiendo que cadena1 es un objeto string.
*      getline( cin.cadena1 );
Lee un objeto string y lo coloca en la cadena1.

18.2. ASIGNACION Y CONCATENACION DE OBJETOS STRING.
*      cadena3.assign( cadena1 );
Se utiliza la función miembro assign para copiar cadena1 en cadena3, se hace una copia separada (cadena1 y cadena3 son objetos independientes).
*      cadenaDestino.assign( cadenaOrigen, inicio, numeroDeCaractes );
En donde cadenaOrigen es el objeto string que se va a copiar, inicio es el subíndice inicial y numeroDeCaracteres es el número de caracteres a copiar.
*      for( int i = 0; i < cadena3.length(); i++)
cout << cadena3.at( i );

La función miembro at proporciona un acceso comprobado (o comprobación de rango); es decir, al ir más allá del final del objeto string se lanza una aceptación out_of_range.
*      string.cadena4( cadena1 + “apulta”);
La cadena4 se declara y se inicializa con el resultado de la concatenación de cadena1 y “apulta”  mediante el uso del operador + sobrecargado, que denota la concatenación.
*      cadena3 += “peta”;
También se utiliza el operador de asignación de suma (+=) para concatenar cadena3 y “pet”.
*      cadena1.append( “acumba” );
Se utiliza la función miembro append para concatenar cadena1 y “acumba”.
*      cadena5.append( cadena1, 4, cadena1.length() - 4);
En esta línea se adjunta la cadena “cumba” a la cadena vacía cadena5. Esta función miembro recibe el objeto string (cadena1) de donde va a obtener los caracteres, el subíndice inicial en el objeto string (4) y el número de caracteres que se deben adjuntar (el valor devuelto por cadena1.length () - 4).

18.3. COMPARACION DE OBJETOS STRING
*      if( cadena1 == cadena4 )
La condición de la línea anterior compara la igualdad entre cadena1 y cadena4, usando el operador de igualdad sobrecargo. Si la condición es true, se imprime “cadena1 == cadena4”. Si la condición es false, se evalúa la condición if (cadena1 > cadena4). Todas las funciones operador sobrecargo de la clase string, devuelven valores bool.
*      int resultado = cadena1.compare( cadena2 );
Se utiliza la función miembro compare de string para comparar cadena1 y cadena2. A la variable resultado se le asigna 0 si los objetos string son equivalentes. Un léxico es un diccionario. Cuando decimos que un objeto string es léxicográficamente menor que otro, indicamos que el método compare utiliza los valores numéricos de los caracteres en cada objeto string para determinar que el primer objeto string es menor que el segundo.
*      resultado = cadena1.compare (1, 4, cadena3, 1, 4 );
Se utiliza la función miembro compare para comparar las porciones de cadena1 y cadena3. Los primeros argumentos (1, 4) especifican el subíndice inicial y la longitud de la porción de la cadena1 (“prueba”) que se va a comparar con cadena3. El 3er argumento es un objeto string de comparación. Los 2 últimos argumentos (1 y 4) son el subíndice inicial de y la longitud de la porción del objeto string de comparación que se va a comparar.
*      resultado = cadena4.compare (0, cadena2.lenght (), cadena2);

En la línea anterior se utiliza compare para comparar cadena4 y cadena2. Los primeros dos argumentos son los mismos: el subíndice inicial y la longitud. El último argumento es el objeto string de comparación. Como las 2 piezas de objetos string que se están comparando aquí son idénticos, a resultado se le asigna 0.
*      resultado = cadena2.compare (0, 3, cadena4);
En esta línea se hace una llamada a la función compare para comparar los primeros 3 caracteres en cadena2 con cadena4.

18.4. SUBCADENAS
La clase string proporciona la función miembro substr para obtener una subcadena de un objeto string. El resultado es un nuevo objeto string que se copia del objeto string de origen.
*      cout << cadena1.substr( 7, 5) << endl;
La línea anterior utiliza la función miembro substr para obtener una subcadena de cadena1. El primer argumento especifica el subíndice inicial de la subcadena deseada, el segundo argumento especifica la longitud de la subcadena.

18.5. INTERCAMBIO DE OBJETOS STRING
La clase string proporciona la función miembro swap para intercambiar objetos string.
*      primero.swap ( segundo );
Se utiliza la función swap para intercambiar los valores de primero y segundo. Los objetos string se imprimen de nuevo para confirmar que se hayan cambiado. La función miembro swap de string es útil para implementar programas que ordenan cadenas.

18.6. CARACTERISTICAS DE LOS OBJETOS STRING
La clase string proporciona funciones miembro para recopilar información acerca del tamaño de un objeto string, su longitud máxima y otras características.
*      El tamaño (función miembro size) o la longitud (función miembro length) de un objeto string es el número de caracteres actualmente almacenados en el objeto string.
*      La capacidad (usando la función miembro capacity) de un objeto string es el número de caracteres que se pueden almacenar en el objeto string sin necesidad de asignar más memoria.
*      El tamaño máximo (usando la función miembro max_size) es el mayor tamaño posible que puede tener un objeto string. Si se excede este valor, se lanza una excepción length_error.
*      Si el objeto está o no vacío se utiliza la función miembro empty.
*      La función miembro resize se utiliza para incrementar la longitud de cadena1 por 10 caracteres. Los elementos adicionales se establecen con caracteres nulos.

18.7. BUSQUEDA DE SUBCADENAS Y CARACTERES EN UN OBJETO STRING.
La clase string proporciona funciones miembro const para buscar subcadenas y caracteres en un objeto string.
*      << ” \n\n(find )\”es\” se encontro en: “ << cadena1.find( “es” )
Esta línea trata de buscar “es” en cadena1, usando la función find.
*      <<”\n(rfind) \ “es\” se encontro en:  ” << cadena1.rfind ( “es” );
En esta línea se utiliza la función miembro rfind para realizar una búsqueda inversa en cadena1(es decir de derecha a izquierda).
*      ubicación = cadena1.find_first_of( “liesop” );
Con la función find_first_of podemos localizar la primera ocurrencia  en cadena1 de cualquier carácter de “liesop”. A búsqueda se realiza desde el inicio de la cadena1.
*      ubicación = cadena1.find_last_of( “liesop” );
Utilizamos la función miembro find_last_of para buscar la última ocurrencia en cadena1 de cualquier carácter de “liesop”. La búsqueda se realiza desde el final de caden1.
*      ubicacion = cadena1.find_first_not_of( “medop ias” );
Utilizamos la función find_first_not_of para buscar el primer carácter en cadena1 que no esté contenida en “medop ias”. La búsqueda se realiza desde el inicio de la cadena1.

18.8. REEMPLAZO DE CARACTERES EN UN OBJETO STRING.
*      cadena1.erase( 72 );
Esta línea de código utiliza la función miembro erase de string para borrar todo, desde el carácter en la posición 62 hasta el final de la cadena1.
*      Int posicion = cadena1.find( “ ” );
*      while( posicion != string::npos ){
*      cadena1.replace( posición, 1, ”.”);
*      posicion = cadena1( “ ”, posicion + 1);  }
En las líneas de código anterior se utiliza la función miembro find para localizar cada ocurrencia el carácter de espacio. Después, cada espacio se reemplaza con un mediante una llamada a la función miembro replace de string. La función replace recibe tres argumentos: el subíndice del carácter en el objeto string en el que se desea ser reemplazado, el número de caracteres a reemplazar y la cadena de reemplazo.

18.9. INSERCIÓN DE CARACTERES EN UN OBJETO STRING.
La clase string proporciona funciones miembro para insertar caracteres en un objeto string.
*      cadena1.insert( 10, cadena2 );
En esta línea de código se utiliza la función miembro insert de string para insertar el contenido de cadena2 antes del elemento 10 de cadena1.
*      cadena3.insert( 3, cadena4, 0, string::npos );
En la línea anterior se utiliza insert para insertar cadena4 antes del elemento 3 de la cadena3. Los últimos 2 argumentos especifican los elementos inicial y final de cadena4 que se deben insertar. Al utilizar string::npos, se inserta todo el objeto string.

18.10. CONVERSIÓN A CADENAS ESTILO C.
La clase string proporciona funciones miembro para convertir objetos de la clase string en cadenas basadas en apuntador estilo C.
*      cadena1.copy( ptr2, longitud, 0);
Se utiliza a función miembro copy de string para copiar el objeto cadena1 en el arreglo char al que apunta ptr2.
*      << cadena1.c_str() << “\nptr1 es ”;
La función c_str sirve para obtener un apuntador const char* que apunte a una cadena estilo C con terminación nula.

18.11. ITERADORES
La clase string proporciona iteradores para realizar recorridos hacia adelante y hacia atrás de objetos string. Los iteradores proporcionan acceso a los caracteres individuales con una sintaxis similar a las operaciones de los apuntadores.
*      string::const_iterador iterador1 = cadena1.begin();
Un const_iterador es un iterador que no puede modificar el objeto string. Existen dos versiones de begin: una devuelve un iterador para iterar a través de un objeto string no const y una versión const que devuelve un const_iterador para iterar con un objeto const_string.
1.      while ( iterador1 != cadena1.end() ){
2.      cout << *iterador1;
3.      iterador1++;
4.      }
En las líneas anteriores se utiliza iterador iterador1 para “recorrer” cadena1. La función miembro end de la clase string devuelve un objeto iterador o const_iterador para la posición que está más allá  del último elemento de cadena1. El iterador se avanza una posición mediante el operador ++.
La clase string proporciona las funciones miembro rend y rbegin para acceder a los caracteres individuales de un objeto string en forma inversa, desde el final hasta su inicio. Las funciones rend y rbegin devuelven objetos reverse_iterador o const_reverse_iterador.

18.12. PROCESAMIENTO DE FLUJOS DE CADENA.
Además de la E/S de flujos  estándar y la E/S de flujos de archivo, en C++ la E/S de flujos incluye herramientas para recibir y enviar datos de objetos string en la memoria. A estas herramientas se le conoce como E/S en memoria o procesamiento de flujos de cadena.
La entrada desde un objeto string esta soportada por la clase istringstream. La salida hacia un objeto string esta soportada por la clase ostringstream. Los nombres son definidos por typedef:
*      Typedef basic_istringstream< char > istringstream;
*      Typedef basic_ostringstream< char > ostringstream;
Estas plantillas proporcionan la misma  funcionalidad que las clases istream y ostream. Los programas que utilizan el formato en memoria deben incluir los archivos de encabezado <sstream> y <iostream>. Un programa puede leer toda una línea a la vez del flujo de entrada y colocarla en un objeto string. Una rutina de validación puede escudriñar el contenido del objeto string y corregir los datos, si es necesario. El programa puede continuar recibiendo datos de entrada, sabiendo que los datos de entrada se encuentran en el formato apropiado.
Un objeto ostringstream utiliza un objeto string para almacenar los datos de salida. La función miembro str de la clase ostringstream devuelve una copia de ese objeto string.
*      ostringstream cadenaSalida;
Con la línea  de código anterior se crea el objeto ostringstream y utiliza el operador de inserción de flujo  para enviar una serie de objetos string y valores numéricos al objeto.
*      cadenaSalida << cadena1 << cadena2 << cadena3 << doble1 << cadena4 << entero << cadena5 << &entero;
Con esta línea se envían los objetos string y la dirección de int entero; todos al objeto cadenaSalida en memoria.
*      cout << “cadenaSalida contiene:\n” << cadenaSalida.str();
En esta línea se utiliza el operador de inserción de flujo y la llamada cadenaSalida.str() para mostrar una copia del objeto string creado.
*      cadenaSalida << “\nse agregaron mas caracteres”;
Con esta línea de código se demuestra que se pueden adjuntar  más datos al objeto string en memoria, con solo enviar otra operación de inserción de flujo a cadenaSalida.
*      cout << “\n\ndespues de las inserciones de flujo adicionales,\n” << “cadenaSalida contiene: \n” << cadenaSalida.str() << endl;
En esta línea se muestra el objeto string cadenaSalida después de adjuntar caracteres adicionales. Un objeto istringstream introduce datos desde un objeto string en memoria a las variables del programa. Los datos son almacenados en un objeto istringstream como caracteres. Las operaciones de entrada desde el objeto istringstream funcionan de manera idéntica a las operaciones de entrada desde cualquier archivo. El objeto istringstream interpreta el final del objeto string como el fin del archivo.
*      String entrada( “Entrada prueba 123 4.7 A” );
*      Istringstream cadena Entrada( entrada );
Con la primera línea de código se crea el objeto string que contiene los datos, y la segunda que se construye para contener los datos en el objeto string entrada. Este objeto contiene los datos que cuando se leen como entrada para el programa, consisten en dos cadenas (“Entrada” y “prueba”), un int (123), un double (4.7) y un char (‘A’).
*      if( cadenaEntrada.good() ){

La condición if en la línea anterior utiliza la función good para probar si quedan datos. Si no quedan datos, la función devuelve false y se ejecuta la siguiente parte de la instrucción if la cual sería else.

No hay comentarios.:

Publicar un comentario