Operadores new e delete

A C++ oferece suporte a alocação e desalocação dinâmica de objetos usando os operadores new e delete. Esses operadores alocam memória para objetos de um pool chamado de free store (também conhecido como heap). O operador new chama a função especial operator new e o operador delete chama a função especial operator delete.

Para ver uma lista dos arquivos da Biblioteca de Runtime C e a Biblioteca Padrão C++, consulte Recursos da Biblioteca de CRT.

O operador new

O compilador converte uma instrução como esta em uma chamada para a função operator new:

char *pch = new char[BUFFER_SIZE];

Se a solicitação for para zero bytes de armazenamento, operator new retorna um ponteiro para um objeto distinto. Ou seja, chamadas repetidas para operator new retornam ponteiros diferentes.

Se não houver memória suficiente para a solicitação de alocação, operator new gerará uma exceção std::bad_alloc. Ou ele retornará nullptr se você tiver usado a forma placementnew(std::nothrow) ou se estiver vinculado no suporte de não lançamento operator new. Para obter mais informações, consulte Comportamento de falha de alocação.

Os dois escopos para funções operator new são descritos na tabela a seguir.

Escopo para funções operator new

Operador Escopo
::operator new Global
class-name::operator new Classe

O primeiro argumento de operator new deve ser do tipo size_t e o tipo de retorno é sempre void*.

A função operator new global é chamada quando o operador new é usado para alocar objetos de tipos internos, objetos de tipo de classe que não contêm funções operator new definidas pelo usuário e matrizes de qualquer tipo. Quando o operador new é usado para alocar objetos de um tipo de classe um operator new é definido, o operator new dessa classe é chamado.

Uma função operator new definida para uma classe é uma função membro estática (que não pode ser virtual), a qual oculta a função operator new global para objetos desse tipo de classe. Considere o caso em que new é usado para alocar e definir memória para um valor específico:

#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;
}

O argumento fornecido entre parênteses para new é passado para Blanks::operator new como o argumento chInit. No entanto, a função operator new global é ocultada, fazendo com que o código como o seguinte gere um erro:

Blanks *SomeBlanks = new Blanks;

O compilador oferece suporte aos operadores new e delete da matriz de membros em uma declaração de classe. Por exemplo:

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

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

Comportamento de falha de alocação

A função new na Biblioteca Padrão C++ dá suporte ao comportamento especificado no padrão C++ desde C++98. Quando não houver memória suficiente para uma solicitação de alocação, operator new gerará uma exceção std::bad_alloc.

O código C++ mais antigo retornou um ponteiro nulo para uma alocação com falha. Se você tiver um código que espera a versão de não lançamento de new, vincule o programa com nothrownew.obj. O arquivo nothrownew.obj substitui operator new global por uma versão que retorna nullptr se uma alocação falhar. operator new não lança mais std::bad_alloc. Para obter mais informações sobre nothrownew.obj e outros arquivos de opção do vinculador, consulte Opções do Link.

Você não pode misturar código que verifica exceções de operator new global com código que verifica ponteiros nulos no mesmo aplicativo. No entanto, você ainda pode criar operator new de classe local que se comporte de forma diferente. Essa possibilidade significa que o compilador deve agir defensivamente por padrão e incluir verificações de retorno de ponteiro nulo em chamadas new. Para obter mais informações sobre uma maneira de otimizar essas verificações do compilador, consulte /Zc:throwingnew.

Tratar condições de memória insuficiente

A maneira como você testa uma alocação com falha de uma new expressão depende se você usa o mecanismo de exceção padrão ou usa um retorno nullptr. O C++ Padrão espera que um alocador gere std::bad_alloc ou uma classe derivada de std::bad_alloc. Você pode tratar essa exceção, conforme mostrado neste exemplo:

#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;
   }
}

Ao usar a forma nothrow de new, é possível testar uma falha de alocação, conforme mostrado neste exemplo:

#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;
   }
}

Você pode testar uma alocação de memória com falha se usou o arquivo nothrownew.obj para substituir operator new global, conforme mostrado aqui:

#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;
   }
}

Você pode fornecer um manipulador para solicitações de alocação de memória com falha. É possível gravar uma rotina de recuperação personalizada para tratar essa falha. Ela pode, por exemplo, liberar alguma memória reservada e permitir que a alocação seja executada novamente. Para obter mais informações, consulte _set_new_handler.

O operador delete

A memória que é atribuída dinamicamente usando o operador newpode ser liberada usando o operador delete. O operador delete chama a função operator delete, que libera memória no pool disponível. Usar o operador delete também faz com que o destruidor da classe (se houver) seja chamado.

Há funções operator delete de escopo global e de classe. Apenas uma função operator delete pode ser definida para uma classe específica e, se definida, ela oculta a função operator delete global. A função operator delete global sempre é chamada para matrizes de qualquer tipo.

A função operator delete global. Existem duas formas para as funções operator delete global e operator delete de membro de classe:

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

Somente uma das duas variantes precedentes pode estar presente para uma determinada classe. A primeira forma usa um argumento do tipo void *, que contém um ponteiro para o objeto a ser desalocado. A segunda forma, desalocação dimensionada, usa dois argumentos: o primeiro é um ponteiro para o bloco de memória a ser desalocado e o segundo é o número de bytes para desalocar. O tipo de retorno de ambos os formulários é void (operator delete não é possível retornar um valor).

A intenção da segunda forma é acelerar a pesquisa pela categoria de tamanho correta do objeto a ser excluído. Essas informações geralmente não são armazenadas perto da alocação em si e provavelmente não são armazenadas em cache. A segunda forma é particularmente útil quando uma função operator delete de uma classe base é usada para excluir um objeto de uma classe derivada.

A função operator delete é estática e, portanto, não pode ser virtual. A função operator delete obedece o controle de acesso, conforme descrito em Controle de Acesso de Membros.

O exemplo a seguir mostra as funções operator new e operator delete definidas pelo usuário criadas para registrar em log as alocações e desalocações de memória:

#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;
}

O código anterior pode ser usado para detectar "vazamento de memória", ou seja, a memória atribuída no repositório livre, mas nunca liberada. Para executar essa detecção, os operadores new e delete globais são redefinidos para contar a alocação e a desalocação de memória.

O compilador oferece suporte aos operadores new e delete da matriz de membros em uma declaração de classe. Por exemplo:

// 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;
}