Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.9.20d   Errores con el operador new

§1  Sinopsis

Cuando la asignación de memoria no puede ser satisfecha, new comprueba si existe una función que en caso de error adopte las medidas pertinentes, y en caso de existir, es invocada sin que se le pase ningún argumento. Esta función puede ser establecida por el usuario mediante ser_new_handler() , pero si no se ha establecido ninguna, entonces new lanza la excepción bad_alloc §3 , a la que debería definirse un capturador adecuado.

Ejemplo:

try {
  char* ptr;
  for (int i = 0; i <= SIZE; i++) { ptr = new char[10]; }
}
catch (const bad_alloc& e) {
  cout << "Memoria agotada " << e.what() << endl;
  abort();
}

Observe que what() es un método de la clase bad_alloc

El comportamiento descrito corresponde a la última versión del Estándar (de Julio de 1998). Algunas implementaciones antiguas [1] utilizan una convención distinta, que las pautas de las antiguas funciones malloc, calloc y realloc de la librería C clásica. Devuelve siempre un puntero no nulo (distinto de cero 4.2.1) si la operación se realiza con éxito, y un valor cero en caso contrario.

Con objeto de permitir que el código escrito conforme a esta última convención pudiese seguir funcionando con los nuevos compiladores, se decidió incluir en estos una versión sobrecargada de los operadores globales que funcionasen al modo antiguo. Son las funciones ::operator new(nothrow) y ::operator new(nothrow)[]. Para que un programa diseñado según el estándar antiguo funcionase correctamente con un compilador que siguiera el nuevo, solo habría que cambiar las sentencias que incluyeran la invocación del operador. Por ejemplo, una versión antigua del código anterior aparecería como:

char* ptr;
for (int i = 0; i <= SIZE; i++) {
   ptr = new char[10];
   if (!ptr) {    // ptr == 0
     cout << "Memoria agotada" << endl;
     abort();
   }
}

Para adaptarla al nuevo estándar habría que transformarla en:

char* ptr;
for (int i = 0; i <= SIZE; i++) {
   ptr = new (std::nothrow) char[10];
   if (!ptr) {    // ptr == 0
     cout << "Memoria agotada" << endl;
     abort();
   }
}


§2  set_new_handler

Se trata de una función de Librería Estándar, en <new.h>, que permite definir una función que será llamada cuando el operador new no pueda satisfacer la asignación de memoria solicitada.

set_new_handler instala la función que será llamada cuando el operador global operator new() u operator new[]() no puedan asignar la memoria requerida. A su vez devuelve un puntero al último manejador si había alguno instalado.

Si no existe ninguna función instalada, el operador new lanza por defecto una excepción bad_alloc en caso de no poder asignar la memoria requerida, pero este comportamiento puede ser alterado mediante una llamada a la función set_new_handler para fijar un nuevo manejador.

§2.1 Sintaxis

new_handler set_new_handler(new_handler mi_puntero);


  new_handler es un typedef ( 3.2.1a) definido en <new.h> como:

typedef void (*new_handler)();

Es decir, un puntero a función que devuelve void que no recibe ningún parámetro.  Así pues, la nueva función que instalemos debe tener estas características (ver ejemplo ).

  mi_puntero es un puntero a la función que queremos instalar. Evidentemente será una función que no acepte argumentos y devuelva void.

Nota: si se quiere mantener compatibilidad hacia atrás con la versión tradicional de new, que no lanzaba por si mismo ninguna excepción, puede utilizarse un puntero nulo como valor de mi_puntero, mediante set_new_handler(0).

§2.3  Esquema de funcionamiento

void MiFuncion () {      // Función que será invocada en caso de fallo

  cout << "Atención memoria agotada..." << endl;

  exit(EXIT_FAILURE);

}

...

{

  new_handler punteroActual;

  new_handler miPuntero = &MiFuncion;

  new_handler miPuntero = MiFuncion;  // alternativa equivalente ( 4.2.4a)

  ...

  punteroActual = set_new_handler(miPuntero);  // MiFuncion queda instalada

  ....

  set_new_handler(punteroActual);     // restituida función anterior

}

Si new no puede asignar la memoria solicitada, invoca el manejador que fue establecido en la última llamada a set_new_handler. Si no hubiese instalado ninguno, new devuelve 0. mi_puntero debe especificar las medidas a tomar en tal caso.

El manejador mi_puntero definido por el usuario debería tomar alguna de las siguientes acciones:

  • Liberar memoria suficiente y terminar (return). En este caso new volverá a intentar satisfacer la demanda de memoria, si tiene éxito el programa continuará. Este sería la opción ideal.

  • Lanzar una excepción bad_alloc o cualquiera derivada de ella.

    Si no puede adoptarse la primera opción, la función señalada por mi_puntero debe lanzar una excepción que termine el programa, pues de lo contrario se entraría en un bucle indefinido.

  • Llamar a las funciones abort  ( 1.5.1) o exit ( 1.5.1)

§2.4  Ejemplo

void agotado() {

   cerr << "Memoria agotada\n";

   throw bad_alloc();

}

 

int main() {                     // ================

   set_new_handler(agotado);    // M1 Instala manejador

   try {

     for (int i = 0; i <= SIZE; i++) { new char[1000]; }

   }

   catch (bad_alloc) {          // M.5

     cout << "El programa termina con error" << endl;

     exit(1);

  }

  cout << "El porgrama ha terminado con éxito" << endl;

}

Comentario

La sentencia M1 instala un manejador para caso de fallo del operador new, que permanecerá activo mientras no se instale otro. Observe que en esta sentencia se toma el nombre de la función en sustitución de su puntero.

En caso de agotarse la memoria en el bloque try, se invocaría la función agotado que fue instalada en M1. Esta función lanza un mensaje de error, y a continuación una excepción que sería recogida en el manejador instalado en M5. Aquí se lanza un mensaje de aviso y se termina el programa con exit ( 1.5.1).

Una opción preferible es sobrecargar los operadores operator new() y operator new[]() de forma que se comporten de la forma adecuada a las necesidades de nuestra aplicación.

§3  bad_alloc

El objeto lanzado por new cuando se produce un error es del tipo bad_alloc, una clase definida en <new.h> con cuya definición tiene el siguiente aspecto:

class bad_alloc : public exception {  /* definición-de-la-clase */  }:

Como puede verse, al igual que el resto de excepciones lanzados por la Librería Estándar ( 1.6.1a), deriva públicamente ( 4.11.2b) de la clase exception.

Su interfaz es la siguiente:

class bad_alloc : public exception {
  public:
  bad_alloc() throw();                        // constructor
  bad_alloc(const bad_alloc&) throw();       // constructor-copia
  bad_alloc& operator=(const bad_alloc&) throw();  // operador de asignación
  virtual ~bad_alloc() throw();              // destructor
  virtual const char* what() const throw();  // descriptor
};

Comentario

La clase tiene cinco métodos públicos; ninguno de los cuales puede lanzar una excepción.

El constructor se limita a iniciar correctamente un objeto de la clase bad_alloc (este método no puede lanzar excepciones).

El constructor-copia y el operador de asignación operator= permiten copiar y asignar objetos de este tipo.

Tiene dos métodos virtuales: el destructor, y un método what() que no acepta argumentos y devuelve un puntero a carácter. El resultado de este último depende de la implementación. Suele ser una cadena (terminada con un carácter nulo) que contiene una explicación relativa al error. 

  Inicio.


[1]  Es el caso del compilador MS VC++ 6.0