Warum wird der Destruktor im Operator delete nicht aufgerufen?

16

Ich habe versucht, ::deleteeine Klasse in der davon zu fordern operator delete. Aber der Destruktor wird nicht aufgerufen.

Ich habe eine Klasse definiert, MyClassderen operator deleteüberladen wurde. Das Globale operator deleteist ebenfalls überlastet. Die Überladung operator deletevon MyClassruft die überladene globale auf 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;
}

Die Ausgabe ist:

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

Tatsächlich:

Es gibt nur einen Aufruf an den destructor , bevor die überladenen Aufruf operator deletevon MyClass.

Erwartet:

Es gibt zwei Aufrufe an den Destruktor. Eine vor dem Aufruf der überladenen operator deletevon MyClass. Ein anderer vor dem Aufruf der globalen operator delete.

expinc
quelle
6
MyClass::operator new()sollte Rohspeicher von (mindestens) sizeBytes zuweisen . Es sollte nicht versucht werden, eine Instanz von vollständig zu erstellen MyClass. Der Konstruktor von MyClasswird nach ausgeführt MyClass::operator new(). Dann ruft der deleteAusdruck in main()den Destruktor auf und gibt den Speicher frei (ohne den Destruktor erneut aufzurufen). Der ::delete pAusdruck hat keine Informationen über die Art der pObjektpunkte an, da peine ist void *, kann so nicht den Destruktor aufrufen.
Peter
Siehe auch
Mooing Duck
2
Die Antworten, die Sie bereits erhalten haben, sind korrekt, aber ich frage mich: Warum versuchen Sie, neue zu überschreiben und zu löschen? Der typische Anwendungsfall ist die Implementierung einer benutzerdefinierten Speicherverwaltung (GC, Speicher, der nicht aus dem Standard-malloc () usw. stammt). Vielleicht verwenden Sie das falsche Werkzeug für das, was Sie erreichen möchten.
Noamtm
2
::delete p;verursacht undefiniertes Verhalten, da der Typ von *pnicht mit dem Typ des zu löschenden Objekts identisch ist (noch eine Basisklasse mit virtuellem Destruktor)
MM
@MM Die großen Compiler warnen höchstens davor, daher habe ich nicht bemerkt, dass void*der Operand sogar explizit schlecht geformt ist. [expr.delete] / 1 : " Der Operand muss vom Zeiger auf den Objekttyp oder vom Klassentyp sein. [...] Dies impliziert, dass ein Objekt nicht mit einem Zeiger vom Typ void gelöscht werden kann, da void kein Objekttyp ist. * "@OP Ich habe meine Antwort geändert.
Walnuss

Antworten:

17

Sie missbrauchen operator newund operator delete. Diese Operatoren sind Zuordnungs- und Freigabefunktionen. Sie sind nicht verantwortlich für die Konstruktion oder Zerstörung von Objekten. Sie sind nur für die Bereitstellung des Speichers verantwortlich, in dem das Objekt platziert wird.

Die globalen Versionen dieser Funktionen sind ::operator newund ::operator delete. ::newund ::deletesind neue / delete-Ausdrücke, ebenso wie new/ delete, die sich darin unterscheiden, ::newund ::deleteumgehen klassenspezifische operator new/ operator deleteÜberladungen.

Die neuen / delete-Ausdrücke konstruieren / zerstören und ordnen / freigeben (durch Aufrufen des entsprechenden operator newoder operator deletevor dem Aufbau oder nach der Zerstörung).

Da Ihre Überlastung nur für den Zuordnungs- / Freigabe-Teil verantwortlich ist, sollte sie ::operator newund ::operator deleteanstelle von ::newund aufrufen ::delete.

Das deletein delete myClass;ist dafür verantwortlich, den Destruktor aufzurufen.

::delete p;ruft den Destruktor nicht auf, da er peinen Typ hat void*und der Ausdruck daher nicht wissen kann, welcher Destruktor aufgerufen werden soll. Wahrscheinlich wird Ihr Ersetzter aufgerufen, ::operator deleteum die Zuordnung des Speichers aufzuheben, obwohl die Verwendung eines void*as-Operanden für einen Löschausdruck falsch ist (siehe Bearbeitung unten).

::new MyClass();ruft Ihren ersetzten ::operator newauf, um Speicher zuzuweisen, und erstellt ein Objekt darin. Der Zeiger auf dieses Objekt wird in Bezug void*auf den neuen Ausdruck in zurückgegeben MyClass* myClass = new MyClass();, der dann ein anderes Objekt in diesem Speicher erstellt und die Lebensdauer des vorherigen Objekts beendet, ohne dessen Destruktor aufzurufen.


Bearbeiten:

Dank des Kommentars von @ MM zu dieser Frage wurde mir klar, dass ein void*Operand für ::deletetatsächlich schlecht geformt ist. ( [expr.delete] / 1 ) Die großen Compiler scheinen jedoch beschlossen zu haben, nur davor zu warnen, nicht vor Fehlern. Sehen Sie sich diese Frage an, bevor es schlecht geformt wurde und ::deleteein void*bereits undefiniertes Verhalten verwendet .

Daher ist Ihr Programm schlecht geformt und Sie haben keine Garantie dafür, dass der Code tatsächlich das tut, was ich oben beschrieben habe, wenn er noch kompiliert werden konnte.


Wie von @SanderDeDycker unter seiner Antwort ausgeführt, haben Sie auch ein undefiniertes Verhalten, da Sie durch das Erstellen eines anderen Objekts im Speicher, das bereits ein MyClassObjekt enthält, ohne zuerst den Destruktor dieses Objekts aufzurufen, gegen [basic.life] / 5 verstoßen, was dies verbietet, wenn das Programm hängt von den Nebenwirkungen des Destruktors ab. In diesem Fall hat die printfAnweisung im Destruktor einen solchen Nebeneffekt.

Nussbaum
quelle
Durch den Missbrauch soll überprüft werden, wie diese Bediener funktionieren. Vielen Dank für Ihre Antwort. Es scheint die einzige Antwort zu sein, die mein Problem löst.
Expinc
13

Ihre klassenspezifischen Überladungen werden falsch ausgeführt. Dies ist in Ihrer Ausgabe zu sehen: Der Konstruktor wird zweimal aufgerufen!

operator newRufen Sie im klassenspezifischen den globalen Operator direkt auf:

return ::operator new(size);

Führen Sie in der Klassenspezifischen Vorgehensweise operator deleteFolgendes aus:

::operator delete(p);

operator newWeitere Informationen finden Sie auf der Referenzseite.

Sander De Dycker
quelle
Ich weiß, dass der Konstruktor zweimal aufgerufen wird, indem :: new im Operator new aufgerufen wird, und ich meine es ernst. Meine Frage ist, warum Destruktor nicht aufgerufen wird, wenn :: delete im Operator delete aufgerufen wird.
Expinc
1
@expinc: Absichtlich Aufruf der Konstruktor ein zweites Mal ohne Aufruf der destructor zuerst eine wirklich schlechte Idee ist. Für nicht triviale Destruktoren (wie Ihre) wagen Sie sich sogar in ein undefiniertes Verhaltensgebiet (wenn Sie von den Nebenwirkungen des Destruktors abhängen, die Sie tun) - ref. [Grundleben] §5 . Tu das nicht.
Sander De Dycker
1

Siehe CPP-Referenz :

operator delete, operator delete[]

Gibt die Zuweisung von Speicher frei, der zuvor durch einen Abgleich zugewiesen wurde operator new. Diese Freigabefunktionen werden durch Löschausdrücke und durch neue Ausdrücke aufgerufen, um die Zuordnung von Speicher aufzuheben, nachdem Objekte mit dynamischer Speicherdauer zerstört (oder nicht erstellt) wurden. Sie können auch mit der regulären Funktionsaufrufsyntax aufgerufen werden.

Löschen (und Neu) sind nur für den Teil 'Speicherverwaltung' verantwortlich.

Es ist also klar und zu erwarten, dass der Destruktor nur einmal aufgerufen wird - um die Instanz des Objekts zu bereinigen. Würde es zweimal aufgerufen, müsste jeder Destruktor prüfen, ob es bereits aufgerufen wurde.

Mario der Löffel
quelle
1
Der Löschoperator sollte den Destruktor weiterhin implizit aufrufen, wie aus seinen eigenen Protokollen hervorgeht, in denen der Destruktor nach dem Löschen der Instanz angezeigt wird. Das Problem hierbei ist, dass seine Klassenlöschüberschreibung :: delete aufruft, was zu seiner globalen Löschüberschreibung führt. Diese globale Löschüberschreibung gibt lediglich Speicher frei, sodass der Destruktor nicht erneut aufgerufen wird.
Pickle Rick
Die Referenz besagt eindeutig, dass Löschen NACH der Dekonstruktion von Objekten genannt wird
Mario The Spoon
Ja, das globale Löschen wird nach dem Löschen der Klasse aufgerufen. Hier gibt es zwei Überschreibungen.
Pickle Rick
2
@PickleRick - Während es wahr ist , dass ein Lösch - Ausdruck ein Destruktoraufrufs sollte (unter der Annahme , einen Zeiger auf einen Typen mit einem destructor mitgeliefert) oder einem Satz von Destruktoren (array Form), einer operator delete()ist Funktion nicht dasselbe wie eine Lösch - Expression. Der Destruktor wird aufgerufen, bevor die operator delete()Funktion aufgerufen wird.
Peter
1
Es wäre hilfreich, wenn Sie der Qoute einen Header hinzufügen würden. Derzeit ist nicht klar, worauf es sich bezieht. "Dell ordnet Speicher zu ..." - wer gibt Speicher frei?
idclev 463035818