J’ai lu quelque part que lorsque vous utilisez le placement nouveau, vous devez appeler le destructeur manuellement.
Considérons le code suivant:
// Allocate memory ourself char* pMemory = new char[ sizeof(MyClass)]; // Construct the object ourself MyClass* pMyClass = new( pMemory ) MyClass(); // The destruction of object is our duty. pMyClass->~MyClass();
Pour autant que je sache, l’opérateur delete
appelle normalement le destructeur, puis libère la mémoire, non? Alors pourquoi n’utilisons-nous pas delete
place?
delete pMyClass; //what's wrong with that?
dans le premier cas, nous sums obligés de définir pMyClass sur nullptr
après avoir appelé destructor comme ceci:
pMyClass->~MyClass(); pMyClass = nullptr; // is that correct?
MAIS le destructeur n’a pas libéré la mémoire, non? Alors, s’agirait-il d’une fuite de mémoire?
Je suis confus, pouvez-vous expliquer cela?
L’utilisation de l’opérateur new
deux effets: il appelle l’ operator new
fonction operator new
qui alloue de la mémoire, puis il utilise le placement new pour créer l’object dans cette mémoire. L’opérateur delete
appelle le destructeur de l’object, puis appelle l’ operator delete
. Oui, les noms sont déroutants.
//normal version calls these two functions MyClass* pMemory = new MyClass; void* pMemory = operator new(sizeof(MyClass)); MyClass* pMyClass = new( pMemory ) MyClass(); //normal version calls these two functions delete pMemory; pMyClass->~MyClass(); operator delete(pMemory);
Étant donné que dans votre cas, vous avez utilisé le placement nouveau manuellement, vous devez également appeler le destructeur manuellement. Comme vous avez alloué manuellement la mémoire, vous devez la libérer manuellement. Rappelez-vous que si vous allouez avec new
, il doit y avoir une delete
. Toujours.
Cependant, le placement nouveau est conçu pour fonctionner également avec les tampons internes (et d’autres scénarios), les tampons n’ayant pas été alloués avec l’ operator new
, c’est pourquoi vous ne devez pas appeler l’ operator delete
sur eux.
#include struct buffer_struct { std::aligned_storage::type buffer; }; int main() { buffer_struct a; MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a //stuff pMyClass->~MyClass(); //can't use delete, because there's no `new`. return 0; }
Le but de la classe buffer_struct
est de créer et de détruire le stockage de quelque manière que ce soit, tandis que main
s’occupe de la construction / destruction de MyClass
, notez à quel point les deux sont (presque *) complètement séparés l’un de l’autre.
* nous devons nous assurer que le stockage doit être suffisamment grand
Une des raisons pour lesquelles c’est faux:
delete pMyClass;
est-ce que vous devez supprimer pMemory
avec delete[]
puisqu’il s’agit d’un tableau:
delete[] pMemory;
Vous ne pouvez pas faire les deux choses ci-dessus.
De même, vous pourriez vous demander pourquoi vous ne pouvez pas utiliser malloc()
pour allouer de la mémoire, placer pour la première fois pour construire un object, puis delete
pour supprimer et libérer la mémoire. La raison en est que vous devez faire correspondre malloc()
et free()
, et non pas malloc()
et delete
.
Dans le monde réel, les appels de destructeurs nouveaux et explicites ne sont presque jamais utilisés. Ils peuvent être utilisés en interne par l’implémentation de la bibliothèque standard (ou pour une programmation au niveau système, comme indiqué dans les commentaires), mais les programmeurs normaux ne les utilisent pas. Je n’ai jamais utilisé de telles astuces pour le code de production depuis de nombreuses années en C ++.
Vous devez faire la distinction entre l’opérateur de delete
et l’ operator delete
. En particulier, si vous utilisez le placement new, vous appelez explicitement le destructeur puis appelez l’ operator delete
(et non l’opérateur delete
) pour libérer la mémoire, c’est-à-dire
X *x = static_cast(::operator new(sizeof(X))); new(x) X; x->~X(); ::operator delete(x);
Notez que cela utilise l’ operator delete
, qui est de niveau inférieur à l’opérateur delete
et ne s’inquiète pas des destructeurs (c’est essentiellement un peu comme free
). Comparez cela à l’opérateur de delete
, qui équivaut en interne à l’appel du destructeur et à l’ operator delete
appelant operator delete
.
Il est intéressant de noter que vous n’avez pas besoin d’utiliser ::operator new
et ::operator delete
pour allouer et désallouer votre tampon – en ce qui concerne le placement new, la manière dont le tampon entre ou est détruit importe peu. L’essentiel est de séparer les problèmes d’allocation de mémoire et de durée de vie des objects.
Incidemment, une application possible de ceci pourrait être dans quelque chose comme un jeu, où vous voudrez peut-être allouer un gros bloc de mémoire à l’avance afin de gérer soigneusement votre utilisation de la mémoire. Vous construirez ensuite des objects dans la mémoire que vous avez déjà acquise.
Une autre utilisation possible serait dans un petit allocateur d’objects de taille fixe optimisé.
Il est probablement plus facile de comprendre si vous imaginez construire plusieurs objects MyClass dans un bloc de mémoire.
Dans ce cas, cela donnerait quelque chose comme:
C’est le genre de chose que vous pourriez faire si vous écrivez un compilateur, un système d’exploitation, etc.
Dans ce cas, j’espère que la raison pour laquelle vous avez besoin d’étapes séparées “destructeur” et “suppression” est claire, car il n’y a aucune raison d’appeler supprimer. Cependant, vous devriez désallouer la mémoire de la manière que vous feriez normalement (free, delete, ne rien faire pour un tableau statique géant, quitter normalement si le tableau fait partie d’un autre object, etc., etc.), et si vous ne le faites pas ll sera divulgué.
Notez également que, comme Greg l’a dit, dans ce cas, vous ne pouvez pas utiliser delete, car vous avez alloué le tableau avec new []. Vous devez donc utiliser delete [].
Notez également que vous devez supposer que vous n’avez pas omis de supprimer delete pour MyClass. Dans le cas contraire, il fera quelque chose de totalement différent qui est presque certainement incompatible avec “nouveau”.
Donc, je pense que vous ne voulez pas appeler «supprimer» comme vous le décrivez, mais est-ce que cela pourrait fonctionner? Je pense que la question est fondamentalement la même que celle-ci: “J’ai deux types non liés qui n’ont pas de destructeurs. Puis-je créer un pointeur sur un type, puis supprimer cette mémoire via un pointeur sur un autre type?” En d’autres termes, “mon compilateur a-t-il une grande liste de tous les éléments alloués, ou peut-il faire différentes choses pour différents types”.
J’ai bien peur de ne pas être sûr. En lisant les spécifications, il est écrit:
5.3.5 … Si le type statique de l’opérande [de l’opérateur de suppression] est différent de son type dynamic, le type statique doit être une classe de base du type dynamic de l’opérande et le type statique doit avoir un destructeur virtuel ou le le comportement est indéfini.
Je pense que cela signifie “Si vous utilisez deux types non liés, cela ne fonctionne pas (vous pouvez supprimer un object de classe de manière polymorphe via un destructeur virtuel)”.
Alors non, ne fais pas ça. Je suppose que cela peut souvent fonctionner dans la pratique si le compilateur regarde uniquement l’adresse et non le type (et que ni l’un ni l’autre n’est une classe à inheritances multiples, qui modifierait l’adresse), mais ne l’essayez pas.