Operadores new y delete

C++ admite la asignación dinámica y la desasignación de objetos mediante los operadores new y delete. Estos operadores asignan memoria para los objetos de un grupo denominado almacén libre (también conocido como montón). El operador new llama a la función especial operator new y el operador delete llama a la función especial operator delete.

Para obtener una lista de los archivos de biblioteca que componen la biblioteca en tiempo de ejecución de C y la biblioteca estándar de C++, consulte Características de la biblioteca CTR.

El operador new

El compilador traduce una instrucción como esta en una llamada a la función operator new:

char *pch = new char[BUFFER_SIZE];

Si la solicitud es para cero bytes de almacenamiento, operator new devuelve un puntero a un objeto distinto. Es decir, al llamar repetidamente a operator new se devuelven punteros diferentes.

Si no hay memoria suficiente para la solicitud de asignación, operator new produce una excepción std::bad_alloc. O bien, devuelve nullptr si ha usado el formulario de colocación de new(std::nothrow) o si ha vinculado en compatibilidad con operator new sin inicio. Para obtener más información, consulte Comportamiento de los errores de asignación.

En la tabla siguiente se describen los dos ámbitos de las funciones operator new.

Ámbito de las funciones operator new

Operador Ámbito
::operator new Global
class-name::operator new Clase

El primer argumento de operator new debe ser de tipo size_t, y el tipo de valor devuelto siempre será void*.

Se llama a la función global operator new cuando el operador new se usa para asignar objetos de tipos integrados, objetos de tipo de clase que no contienen funciones operator new definidas por el usuario y matrices de cualquier tipo. Cuando el operador new se usa para asignar objetos de un tipo de clase en la que se ha definido operator new, se llama a la función operator new de esa clase.

Una función operator new definida para una clase es una función miembro estática (que no puede ser virtual) que oculta la función global operator new para los objetos de ese tipo de clase. Imagine que se usa new para asignar y establecer la memoria en un valor determinado:

#include <malloc.h>
#include <memory.h>

class Blanks
{
public:
    Blanks(){}
    void *operator new( size_t stAllocateBlock, char chInit );
};
void *Blanks::operator new( size_t stAllocateBlock, char chInit )
{
    void *pvTemp = malloc( stAllocateBlock );
    if( pvTemp != 0 )
        memset( pvTemp, chInit, stAllocateBlock );
    return pvTemp;
}
// For discrete objects of type Blanks, the global operator new function
// is hidden. Therefore, the following code allocates an object of type
// Blanks and initializes it to 0xa5
int main()
{
   Blanks *a5 = new(0xa5) Blanks;
   return a5 != 0;
}

El argumento proporcionado entre paréntesis a new se pasa a Blanks::operator new como el argumento chInit. Sin embargo, se oculta la función global operator new, haciendo que código como el siguiente genere un error:

Blanks *SomeBlanks = new Blanks;

El compilador admite los operadores new y delete de la matriz de miembro en una declaración de clase. Por ejemplo:

class MyClass
{
public:
   void * operator new[] (size_t)
   {
      return 0;
   }
   void   operator delete[] (void*)
   {
   }
};

int main()
{
   MyClass *pMyClass = new MyClass[5];
   delete [] pMyClass;
}

Comportamiento de los errores de asignación

La función new de la biblioteca estándar de C++ admite el comportamiento especificado en el estándar de C++ desde C++98. Cuando no hay memoria suficiente para una solicitud de asignación, operator new produce una excepción std::bad_alloc.

El código de C++ anterior devolvió un puntero nulo para una asignación con errores. Si tiene código que espera la versión no iniciada de new, vincule el programa con nothrownew.obj. El archivo nothrownew.obj reemplaza al operator new global con una versión que devuelve nullptr si se produce un error en una asignación. operator new ya no produce std::bad_alloc. Para obtener más información sobre nothrownew.obj y otros archivos de opciones del enlazador, consulte Opciones de vínculo.

No se puede mezclar código que comprueba si hay excepciones del operator new global con código que comprueba si hay punteros nulos en la misma aplicación. Sin embargo, sigue pudiendo crear un elemento operator new local de clase que se comporte de forma diferente. Esta posibilidad significa que el compilador debe actuar defensivamente de manera predeterminada e incluir comprobaciones para devoluciones de punteros nulos en las llamadas a new. Para obtener más información sobre cómo optimizar estas comprobaciones del compilador, consulte /Zc:throwingnew.

Controlar la memoria insuficiente

La forma en que se prueba una asignación con errores de una expresión new depende de si se usa el mecanismo de excepción estándar o se usa una devolución nullptr. C++ estándar espera que un asignador produzca std::bad_alloc o una clase derivada de std::bad_alloc. Puede controlar una excepción como esta, como se muestra en este ejemplo:

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   try {
      int *pI = new int[BIG_NUMBER];
   }
   catch (bad_alloc& ex) {
      cout << "Caught bad_alloc: " << ex.what() << endl;
      return -1;
   }
}

Cuando se usa la forma nothrow de new, puede probar si se produce un error de asignación, como se muestra en este ejemplo:

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   int *pI = new(nothrow) int[BIG_NUMBER];
   if ( pI == nullptr ) {
      cout << "Insufficient memory" << endl;
      return -1;
   }
}

Puede probar si se produce una asignación de memoria con errores cuando haya usado el archivo nothrownew.obj para reemplazar el operator new global, como se muestra aquí:

#include <iostream>
#include <new>
using namespace std;
#define BIG_NUMBER 10000000000LL
int main() {
   int *pI = new int[BIG_NUMBER];
   if ( !pI ) {
      cout << "Insufficient memory" << endl;
      return -1;
   }
}

Puede proporcionar un controlador para las solicitudes de asignación de memoria con errores. Es posible escribir una rutina de recuperación personalizada para controlar este tipo de errores. Por ejemplo, podría liberar memoria reservada y, a continuación, permitir que la asignación se ejecute de nuevo. Para obtener más información, vea _set_new_handler.

El operador delete

La memoria que se asigna dinámicamente mediante el operador new se puede liberar con el operador delete. El operador delete llama a la función operator delete, que libera memoria para que la use el grupo disponible. El uso del operador delete también ocasiona una llamada al destructor de clase (si existe alguno).

Hay funciones operator delete globales y de ámbito de clase. Solo se puede definir una función operator delete para una clase dada; si se define, oculta la función operator delete global. La función operator delete global siempre se llama para las matrices de cualquier tipo.

La función operator delete global. Existen dos formularios para las funciones operator delete global y operator delete de miembro de clase:

void operator delete( void * );
void operator delete( void *, size_t );

Solo uno de los dos formularios anteriores puede estar presente para una clase determinada. El primer formulario toma un único argumento del tipo void *, que contiene un puntero al objeto que se debe desasignar. El segundo formulario, una desasignación con tamaño, toma dos argumentos: el primero es un puntero al bloque de memoria que se va a desasignar y el segundo es el número de bytes que se van a desasignar. El tipo de valor devuelto de ambos formularios es void (operator delete no puede devolver un valor).

La intención del segundo formulario es acelerar la búsqueda de la categoría de tamaño correcta del objeto que se va a eliminar. Esta información a menudo no se almacena cerca de la propia asignación y es probable que no se almacene en caché. El segundo formulario resulta especialmente útil cuando se usa una función operator delete de una clase base para eliminar un objeto de una clase derivada.

La función operator delete es estática, por lo que no puede ser virtual. La función operator delete obedece al control de acceso, tal como se describe en Control de acceso a miembros.

En el ejemplo siguiente se muestran las funciones operator new y operator delete definidas por el usuario, diseñadas para registrar asignaciones y desasignaciones de memoria:

#include <iostream>
using namespace std;

int fLogMemory = 0;      // Perform logging (0=no; nonzero=yes)?
int cBlocksAllocated = 0;  // Count of blocks allocated.

// User-defined operator new.
void *operator new( size_t stAllocateBlock ) {
   static int fInOpNew = 0;   // Guard flag.

   if ( fLogMemory && !fInOpNew ) {
      fInOpNew = 1;
      clog << "Memory block " << ++cBlocksAllocated
          << " allocated for " << stAllocateBlock
          << " bytes\n";
      fInOpNew = 0;
   }
   return malloc( stAllocateBlock );
}

// User-defined operator delete.
void operator delete( void *pvMem ) {
   static int fInOpDelete = 0;   // Guard flag.
   if ( fLogMemory && !fInOpDelete ) {
      fInOpDelete = 1;
      clog << "Memory block " << cBlocksAllocated--
          << " deallocated\n";
      fInOpDelete = 0;
   }

   free( pvMem );
}

int main( int argc, char *argv[] ) {
   fLogMemory = 1;   // Turn logging on
   if( argc > 1 )
      for( int i = 0; i < atoi( argv[1] ); ++i ) {
         char *pMem = new char[10];
         delete[] pMem;
      }
   fLogMemory = 0;  // Turn logging off.
   return cBlocksAllocated;
}

El código anterior se puede utilizar para detectar "fugas de memoria", es decir, la memoria que se asigna en el almacén libre pero que nunca se libera. Para detectar fugas, los operadores new y delete globales se redefinen para determinar la asignación y desasignación de memoria.

El compilador admite los operadores new y delete de la matriz de miembro en una declaración de clase. Por ejemplo:

// spec1_the_operator_delete_function2.cpp
// compile with: /c
class X  {
public:
   void * operator new[] (size_t) {
      return 0;
   }
   void operator delete[] (void*) {}
};

void f() {
   X *pX = new X[5];
   delete [] pX;
}