Pourquoi le destructeur n'est-il pas appelé dans la suppression de l'opérateur?

16

J'ai essayé d'appeler ::deletepour une classe dans le operator deletede celui-ci. Mais le destructeur n'est pas appelé.

J'ai défini une classe MyClassqui operator deletea été surchargée. Le global operator deleteest également surchargé. La surcharge operator deletede MyClassnommerez mondiale surchargée operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

La sortie est:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Réel:

Il n'y a qu'un seul appel au destructeur avant d'appeler le surchargé operator deletede MyClass.

Attendu:

Il y a deux appels au destructeur. Un avant d'appeler le surchargé operator deletede MyClass. Un autre avant d'appeler le mondial operator delete.

expinc
la source
6
MyClass::operator new()devrait allouer de la mémoire brute, d'au moins ( sizeoctets ) . Il ne doit pas tenter de construire complètement une instance de MyClass. Le constructeur de MyClassest exécuté après MyClass::operator new(). Ensuite, l' deleteexpression dans main()appelle le destructeur et libère la mémoire (sans appeler à nouveau le destructeur). L' ::delete pexpression ne contient aucune information sur le type de ppoint d' objet , car pest un void *, elle ne peut donc pas appeler le destructeur.
Peter
EN RELATION
2
Les réponses qui vous sont déjà données sont correctes, mais je me demande: pourquoi essayez-vous de remplacer le nouveau et le supprimer? Le cas d'utilisation typique est l'implémentation d'une gestion de mémoire personnalisée (GC, mémoire qui ne provient pas du malloc () par défaut, etc.). Peut-être que vous utilisez le mauvais outil pour ce que vous essayez de réaliser.
noamtm
2
::delete p;provoque un comportement indéfini car le type de *pn'est pas le même que le type de l'objet à supprimer (ni une classe de base avec destructeur virtuel)
MM
@MM Les principaux compilateurs ne font que l'avertir au maximum, donc je ne savais pas que l' void*opérande était même explicitement mal formé. [expr.delete] / 1 : " L'opérande doit être du pointeur vers le type d'objet ou du type de classe. [...] Cela implique qu'un objet ne peut pas être supprimé à l'aide d'un pointeur de type void car void n'est pas un type d'objet. * "@OP J'ai modifié ma réponse.
noyer

Réponses:

17

Vous abusez operator newet operator delete. Ces opérateurs sont des fonctions d'allocation et de désallocation. Ils ne sont pas responsables de la construction ou de la destruction d'objets. Ils sont uniquement responsables de fournir la mémoire dans laquelle l'objet sera placé.

Les versions globales de ces fonctions sont ::operator newet ::operator delete. ::newet ::deletesont nouvelles / delete-expressions, tout comme new/ delete, différentes de celles dans la mesure où ::newet ::deleteclasse spécifiques contournent operator new/ operator deletesurcharges.

Les nouvelles / delete-expressions construisent / détruisent et allouent / désallouent (en appelant la appropriée operator newou operator deleteavant la construction ou après la destruction).

Étant donné que votre surcharge n'est responsable que de la partie allocation / désallocation, elle doit appeler ::operator newet ::operator deleteau lieu de ::newet ::delete.

L' deleteentrée delete myClass;est chargée d'appeler le destructeur.

::delete p;n'appelle pas le destructeur car pa type void*et par conséquent l'expression ne peut pas savoir quel destructeur appeler. Il appellera probablement votre remplaçant ::operator deletepour désallouer la mémoire, bien que l'utilisation d'un void*opérande as pour une expression de suppression soit mal formée (voir modification ci-dessous).

::new MyClass();appelle votre remplacée ::operator newpour allouer de la mémoire et y construit un objet. Le pointeur vers cet objet est renvoyé quant void*à la nouvelle expression dans MyClass* myClass = new MyClass();, qui va ensuite construire un autre objet dans cette mémoire, mettant fin à la durée de vie de l'objet précédent sans appeler son destructeur.


Éditer:

Grâce au commentaire de @ MM sur la question, j'ai réalisé qu'un void*opérande as ::deleteest en fait mal formé. ( [expr.delete] / 1 ) Cependant, les principaux compilateurs semblent avoir décidé de ne prévenir que de cela, pas d'erreur. Avant de devenir mal formé, d'utiliser ::deleteun void*comportement déjà indéfini, voir cette question .

Par conséquent, votre programme est mal formé et vous n'avez aucune garantie que le code fait réellement ce que j'ai décrit ci-dessus s'il parvient toujours à compiler.


Comme l'a souligné @SanderDeDycker ci-dessous sa réponse, vous avez également un comportement indéfini car en construisant un autre objet dans la mémoire qui contient déjà un MyClassobjet sans appeler d'abord le destructeur de cet objet, vous violez [basic.life] / 5 qui interdit de le faire si le programme dépend des effets secondaires du destructeur. Dans ce cas, l' printfinstruction dans le destructeur a un tel effet secondaire.

noyer
la source
L'abus est destiné à vérifier le fonctionnement de ces opérateurs. Merci cependant pour votre réponse. Il semble que la seule réponse qui résout mon problème.
expinc
13

Vos surcharges spécifiques à la classe ne sont pas effectuées correctement. Cela peut être vu dans votre sortie: le constructeur est appelé deux fois!

Dans la classe spécifique operator new, appelez directement l'opérateur global:

return ::operator new(size);

De même, dans la classe spécifique operator delete, faites:

::operator delete(p);

Référez-vous à la operator newpage de référence pour plus de détails.

Sander De Dycker
la source
Je sais que le constructeur est appelé deux fois en appelant :: new dans l'opérateur new et je le pense. Ma question est pourquoi le destructeur n'est pas appelé lors de l'appel de :: delete dans l'opérateur delete?
expinc
1
@expinc: Appeler intentionnellement le constructeur une deuxième fois sans appeler le destructeur en premier est une très mauvaise idée. Pour les destructeurs non triviaux (comme le vôtre), vous vous aventurez même dans un territoire de comportement indéfini (si vous dépendez des effets secondaires du destructeur, ce que vous faites) - réf. [basic.life] §5 . Ne fais pas ça.
Sander De Dycker
1

Voir la référence du RPC :

operator delete, operator delete[]

Désalloue le stockage précédemment alloué par une correspondance operator new. Ces fonctions de désallocation sont appelées par des expressions de suppression et par de nouvelles expressions pour désallouer de la mémoire après avoir détruit (ou échoué à construire) des objets avec une durée de stockage dynamique. Ils peuvent également être appelés à l'aide de la syntaxe d'appel de fonction régulière.

Supprimer (et nouveau) ne sont responsables que de la partie «gestion de la mémoire».

Il est donc clair et attendu que le destructeur n'est appelé qu'une seule fois - pour nettoyer l'instance de l'objet. S'il était appelé deux fois, chaque destructeur devrait vérifier s'il avait déjà été appelé.

Mario la cuillère
la source
1
L'opérateur de suppression doit toujours appeler implicitement le destructeur, comme vous pouvez le voir dans ses propres journaux montrant le destructeur après la suppression de l'instance. Le problème ici est que sa substitution de suppression de classe appelle :: delete, ce qui conduit à sa suppression de suppression globale. Cette suppression globale supprime simplement la mémoire afin de ne pas réinvoquer le destructeur.
Pickle Rick
La référence indique clairement que la suppression est appelée APRÈS la déconstruction d'objet
Mario The Spoon
Oui, la suppression globale est appelée après la suppression de classe. Il y a deux remplacements ici.
Pickle Rick
2
@PickleRick - bien qu'il soit vrai qu'une expression de suppression doit appeler un destructeur (en supposant fourni un pointeur vers un type avec un destructeur) ou un ensemble de destructeurs (sous forme de tableau), une operator delete()fonction n'est pas la même chose qu'une expression de suppression. Le destructeur est appelé avant l'appel de la operator delete()fonction.
Peter
1
Il serait utile que vous ajoutiez un en-tête à la qoute. Actuellement, il n'est pas clair à quoi il se réfère. "Dellocate le stockage ..." - qui désalloue le stockage?
idclev 463035818