Partager via


Opérateurs new et delete

C++ prend en charge l’allocation et la désallocation dynamiques d’objets à l’aide des opérateurs new et delete. Ces opérateurs allouent de la mémoire aux objets à partir d’un pool appelé magasin libre (également appelé tas). L’opérateur new appelle la fonction spéciale operator new, et l’opérateur delete appelle la fonction spéciale operator delete.

Pour obtenir la liste des fichiers bibliothèques de la bibliothèque runtime C et de la bibliothèque C++ Standard, consultez Fonctionnalités de la bibliothèque CRT.

L’opérateur new

Le compilateur traduit une instruction telle que celle-ci en appel de la fonction operator new :

char *pch = new char[BUFFER_SIZE];

Si la requête porte sur zéro octet de stockage, operator new retourne un pointeur vers un objet distinct. En d’autres termes, les appels répétés à operator new retournent des pointeurs distincts.

Si la mémoire est insuffisante pour la requête d’allocation, operator new lève une exception std::bad_alloc. Ou bien, il retourne nullptr si vous avez utilisé la forme de dispositionnew(std::nothrow), ou si vous avez lié une prise en charge de operator new qui ne lève pas d’exceptions. Pour plus d’informations, consultez Comportement en cas d’échec d’allocation.

Les deux étendues des fonctions operator new sont décrites dans le tableau suivant.

Étendue des fonctions operator new

Opérateur Étendue
::operator new Global
nom de la classe::operator new Classe

Le premier argument de operator new doit être de type size_t, et le type de retour est toujours void*.

La fonction operator new globale est appelée quand l’opérateur new est utilisé pour allouer des objets de types intégrés, des objets de type classe qui ne contiennent pas de fonctions operator new définies par l’utilisateur ainsi que des tableaux de n’importe quel type. Quand l’opérateur new est utilisé pour allouer les objets d’un type de classe où operator new est défini, le operator new de cette classe est appelé.

Une fonction operator new définie pour une classe est une fonction membre statique (qui ne peut pas être virtuelle) qui masque la fonction operator new globale pour les objets de ce type de classe. Prenons le cas où new est utilisé pour allouer de la mémoire, et lui affecter une valeur donnée :

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

L’argument fourni entre parenthèses à new est passé à Blanks::operator new en tant qu’argument chInit. Toutefois, la fonction operator new globale est masquée, ce qui entraîne la génération d’une erreur dans le code suivant :

Blanks *SomeBlanks = new Blanks;

Le compilateur prend en charge les opérateurs new et delete des tableaux membres dans une déclaration de classe. Par exemple :

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

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

Comportement en cas d’échec d’allocation

La fonction new de la bibliothèque C++ Standard prend en charge le comportement spécifié dans la norme C++ depuis C++98. Quand la mémoire est insuffisante pour une requête d’allocation, operator new lève une exception std::bad_alloc.

Le code des anciennes versions de C++ retournait un pointeur null en cas d’échec d’allocation. Si vous avez du code qui attend la version de new qui ne lève pas d’exceptions, liez votre programme à nothrownew.obj. Le fichier nothrownew.obj remplace la fonction operator new globale par une version qui retourne nullptr en cas d’échec d’une allocation. operator new ne lève plus std::bad_alloc. Pour plus d’informations sur nothrownew.obj et d’autres fichiers d’options de l’éditeur de liens, consultez Options de liaison.

Vous ne pouvez pas mélanger du code qui vérifie les exceptions de la fonction operator new globale avec du code qui vérifie les pointeurs null dans la même application. Toutefois, vous pouvez toujours créer une fonction operator new spécifique à une classe, qui se comporte différemment. Cette possibilité signifie que le compilateur doit agir de manière défensive par défaut, et inclure des contrôles pour les retours de pointeurs null dans les appels de new. Pour plus d’informations sur l’optimisation de ces vérifications du compilateur, consultez /Zc:throwingnew.

Gestion d'une mémoire insuffisante

La façon dont vous testez un échec d’allocation à partir d’une expression new varie selon que vous utilisez le mécanisme d’exception standard ou un retour de nullptr. C++ Standard s’attend à ce qu’un allocateur lève std::bad_alloc ou une classe dérivée de std::bad_alloc. Vous pouvez prendre en charge ce genre d’exception, comme le montre cet exemple :

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

Quand vous utilisez la forme nothrow de new, vous pouvez tester un échec d’allocation, comme le montre cet exemple :

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

Vous pouvez tester un échec d’allocation de mémoire quand vous avez utilisé le fichier nothrownew.obj pour remplacer la fonction operator new globale, comme indiqué ici :

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

Vous pouvez fournir un gestionnaire pour les échecs des requêtes d’allocation de mémoire. Vous pouvez écrire une routine de récupération personnalisée pour gérer ce genre de défaillance. Elle peut, par exemple, libérer une partie de la mémoire réservée, puis permettre la réexécution de l’allocation. Pour plus d’informations, consultez _set_new_handler.

L’opérateur delete

La mémoire allouée dynamiquement à l’aide de l’opérateur new peut être libérée avec l’opérateur delete. L’opérateur delete appelle la fonction operator delete, qui libère de la mémoire dans le pool disponible. L’utilisation de l’opérateur delete entraîne également l’appel du destructeur de classe (le cas échéant).

Il existe des fonctions operator delete qui sont globales, et d’autres dont l’étendue est spécifique à une classe. Une seule fonction operator delete peut être définie pour une classe donnée. Si elle est définie, elle masque la fonction operator delete globale. La fonction operator delete globale est toujours appelée pour les tableaux de tout type.

Fonction operator delete globale. Il en existe deux formes, les fonctions operator delete globales et les fonctions operator delete membres de classe :

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

Une seule des deux formes précédentes peut être présente pour une classe donnée. La première forme accepte un seul argument de type void *, qui contient un pointeur vers l’objet à libérer. La deuxième forme, la désallocation dimensionnée, accepte deux arguments : le premier est un pointeur vers le bloc de mémoire à libérer, et le second est le nombre d’octets à libérer. Le type de retour des deux formes est void (operator delete ne peut pas retourner de valeur).

L’objectif de la deuxième forme est d’accélérer la recherche de la catégorie de taille appropriée de l’objet à supprimer. Bien souvent, ces informations ne sont pas stockées à proximité de l’allocation elle-même, et ne sont probablement pas mises en cache. La deuxième forme est utile quand une fonction operator delete d’une classe de base est utilisée pour supprimer un objet d’une classe dérivée.

La fonction operator delete est statique, elle ne peut donc pas être virtuelle. La fonction operator delete obéit au contrôle d’accès, comme indiqué dans Contrôle d’accès aux membres.

L’exemple suivant montre les fonctions operator new et operator delete définies par l’utilisateur, et conçues pour journaliser les allocations et les désallocations de mémoire :

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

Le code précédent peut être utilisé pour détecter les « fuites de mémoire », c’est-à-dire la mémoire allouée dans le magasin libre mais jamais libérée. Pour détecter les fuites, les opérateurs new et delete globaux sont redéfinis afin de compter les allocations et désallocations de mémoire.

Le compilateur prend en charge les opérateurs new et delete des tableaux membres dans une déclaration de classe. Par exemple :

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