web-dev-qa-db-fra.com

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

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

J'ai défini une classe MyClass dont operator delete A été surchargé. Le operator delete Global est également surchargé. Le operator delete Surchargé de MyClass appellera le global operator delete Surchargé.

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 operator delete Surchargé de MyClass.

Attendu:

Il y a deux appels au destructeur. Un avant d'appeler le operator delete Surchargé de MyClass. Un autre avant d'appeler le global operator delete.

16
expinc

Vous utilisez mal operator new Et 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 new Et ::operator delete. ::new Et ::delete Sont de nouvelles expressions/delete, tout comme new/delete, différentes de celles-ci, en ce que ::new Et ::delete Contournera les surcharges spécifiques à la classe operator new/operator delete.

Les nouvelles/delete-expressions construisent/détruisent et allouent/désallouent (en appelant le operator new Ou operator delete Avant construction ou après destruction).

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

Le delete dans delete myClass; Est responsable de l'appel du destructeur.

::delete p; N'appelle pas le destructeur car p a le type void* Et par conséquent l'expression ne peut pas savoir quel destructeur appeler. Il appellera probablement votre ::operator delete Remplacé pour désallouer la mémoire, bien qu'utilisant un void* Comme opérande à une expression de suppression est mal formé (voir la modification ci-dessous).

::new MyClass(); appelle votre ::operator new remplacé pour allouer de la mémoire et y construit un objet. Le pointeur vers cet objet est renvoyé sous la forme void* À la nouvelle expression dans MyClass* myClass = new MyClass();, qui construira ensuite 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 @ M.M sur la question, j'ai réalisé qu'un void* Comme opérande de ::delete Est 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 qu'il ne soit mal formé, utiliser ::delete Sur un void* Avait déjà un comportement non dé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 objet MyClass sans appeler d'abord le destructeur de cet objet, vous violez [de base. life]/5 ce qui interdit de le faire si le programme dépend des effets secondaires du destructeur. Dans ce cas, l'instruction printf dans le destructeur a un tel effet secondaire.

17
walnut

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, faire:

::operator delete(p);

Se référer au operator new page de référence pour plus de détails.

13
Sander De Dycker

Voir Référence CPP :

operator delete, operator delete[]

Désalloue le stockage précédemment alloué par un operator new. Ces fonctions de désallocation sont appelées par des expressions de suppression et par de nouvelles expressions pour désallouer 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é.

1
Mario The Spoon