Ich dachte: Sie sagen, wenn Sie den Destruktor manuell aufrufen, machen Sie etwas falsch. Aber ist das immer so? Gibt es Gegenbeispiele? Situationen, in denen es notwendig ist, es manuell aufzurufen, oder in denen es schwierig / unmöglich / unpraktisch ist, es zu vermeiden?
c++
coding-style
destructor
Violette Giraffe
quelle
quelle
new
, um ein neues Objekt anstelle des alten zu initialisieren. Im Allgemeinen keine gute Idee, aber es ist nicht ungewöhnlich.Antworten:
Das manuelle Aufrufen des Destruktors ist erforderlich, wenn das Objekt mit einer überladenen Form von erstellt wurde
operator new()
, außer bei Verwendung der "std::nothrow
" Überladungen:Außerhalb Speicherverwaltung auf einem eher niedrigen Niveau wie oben Destruktoren explizit, jedoch fordern, ist ein Zeichen für schlechtes Design. Wahrscheinlich ist es nicht nur schlechtes Design, sondern völlig falsch (ja, die Verwendung eines expliziten Destruktors, gefolgt von einem Aufruf eines Kopierkonstruktors im Zuweisungsoperator, ist ein schlechtes Design und wahrscheinlich falsch).
In C ++ 2011 gibt es einen weiteren Grund für die Verwendung expliziter Destruktoraufrufe: Bei Verwendung von verallgemeinerten Vereinigungen muss das aktuelle Objekt explizit zerstört und ein neues Objekt mithilfe der Platzierung new erstellt werden, wenn der Typ des dargestellten Objekts geändert wird. Wenn die Vereinigung zerstört wird, muss der Destruktor des aktuellen Objekts explizit aufgerufen werden, wenn eine Zerstörung erforderlich ist.
quelle
operator new
" zu sagen , lautet der korrekte Ausdruck "Verwendenplacement new
".operator new(std::size_t, void*)
(und die Array-Variation) spreche , sondern über alle überladenen Versionen vonoperator new()
.temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong
. Warum sagst du das? Ich würde denken, wenn der Destruktor trivial oder nahezu trivial ist, hat er nur minimalen Overhead und erhöht die Verwendung des DRY-Prinzips. Wenn es in solchen Fällen mit einem Zug verwendet wirdoperator=()
, ist es möglicherweise sogar besser als die Verwendung eines Swaps. YMMV.virtual
Funktionen hat (dievirtual
Funktionen werden nicht neu erstellt) und das Objekt ansonsten nur teilweise [neu] konstruiert wird.Alle Antworten beschreiben bestimmte Fälle, aber es gibt eine allgemeine Antwort:
Sie rufen den dtor jedes Mal explizit auf, wenn Sie das Objekt nur zerstören müssen (im C ++ - Sinne), ohne den Speicher freizugeben, in dem sich das Objekt befindet.
Dies geschieht normalerweise in allen Situationen, in denen die Speicherzuweisung / -freigabe unabhängig von der Objektkonstruktion / -zerstörung verwaltet wird. In diesen Fällen erfolgt die Konstruktion durch Platzierung neu auf einem vorhandenen Speicherblock, und die Zerstörung erfolgt durch expliziten dtor-Aufruf.
Hier ist das rohe Beispiel:
Ein weiteres bemerkenswertes Beispiel ist die Standardeinstellung
std::allocator
bei Verwendung durchstd::vector
: Elemente werdenvector
während erstelltpush_back
, aber der Speicher wird in Blöcken zugewiesen, sodass die Elementkonstruktion bereits vorhanden ist. Und dahervector::erase
müssen die Elemente zerstört werden, aber nicht unbedingt wird der Speicher freigegeben (insbesondere, wenn bald ein neuer push_back erfolgen muss ...).Es ist "schlechtes Design" im strengen OOP-Sinne (Sie sollten Objekte verwalten, nicht Speicher: Die Tatsache, dass Objekte Speicher benötigen, ist ein "Vorfall"), es ist "gutes Design" in "Low-Level-Programmierung" oder in Fällen, in denen Speicher vorhanden ist Nicht aus dem "Free Store" entnommen,
operator new
kauft der Standard ein.Es ist schlechtes Design, wenn es zufällig um den Code herum passiert, es ist gutes Design, wenn es lokal für Klassen passiert, die speziell für diesen Zweck entwickelt wurden.
quelle
Nein, Sie sollten es nicht explizit aufrufen, da es zweimal aufgerufen würde. Einmal für den manuellen Aufruf und ein anderes Mal, wenn der Bereich, in dem das Objekt deklariert ist, endet.
Z.B.
Wenn Sie wirklich dieselben Vorgänge ausführen müssen, sollten Sie eine separate Methode haben.
Es gibt eine bestimmte Situation, in der Sie möglicherweise einen Destruktor für ein dynamisch zugewiesenes Objekt mit einer Platzierung aufrufen möchten,
new
aber es klingt nicht nach etwas, das Sie jemals benötigen werden.quelle
Nein, hängt von der Situation ab, manchmal ist es legitim und gutes Design.
Um zu verstehen, warum und wann Sie Destruktoren explizit aufrufen müssen, schauen wir uns an, was mit "neu" und "löschen" passiert.
So erstellen Sie ein Objekt dynamisch
T* t = new T;
unter der Haube: 1. Größe des (T) Speichers wird zugewiesen. 2. Der Konstruktor von T wird aufgerufen, um den zugewiesenen Speicher zu initialisieren. Der Operator new macht zwei Dinge: Zuweisung und Initialisierung.So zerstören Sie das Objekt
delete t;
unter der Haube: 1. Der Destruktor von T wird aufgerufen. 2. Der für dieses Objekt zugewiesene Speicher wird freigegeben. Der Operator delete führt außerdem zwei Aktionen aus: Zerstörung und Freigabe.Man schreibt den Konstruktor, um die Initialisierung durchzuführen, und den Destruktor, um die Zerstörung durchzuführen. Wenn Sie den Destruktor explizit aufrufen, erfolgt nur die Zerstörung, nicht jedoch die Freigabe .
Eine legitime Verwendung des expliziten Aufrufs des Destruktors könnte daher sein: "Ich möchte nur das Objekt zerstören, aber ich kann (oder kann) die Speicherzuordnung (noch) nicht freigeben."
Ein häufiges Beispiel hierfür ist die Vorbelegung des Speichers für einen Pool bestimmter Objekte, die ansonsten dynamisch zugewiesen werden müssen.
Wenn Sie ein neues Objekt erstellen, erhalten Sie den Speicherblock aus dem vorab zugewiesenen Pool und führen eine "Platzierung neu" durch. Nachdem Sie mit dem Objekt fertig sind, möchten Sie möglicherweise den Destruktor explizit aufrufen, um die Bereinigungsarbeiten abzuschließen, falls vorhanden. Sie werden den Speicher jedoch nicht freigeben, wie dies beim Löschen durch den Operator der Fall gewesen wäre. Stattdessen geben Sie den Block zur Wiederverwendung in den Pool zurück.
quelle
Wie in den FAQ angegeben, sollten Sie den Destruktor explizit aufrufen, wenn Sie die Platzierung neu verwenden .
Ich stimme jedoch zu, dass dies selten benötigt wird.
quelle
Jedes Mal, wenn Sie die Zuordnung von der Initialisierung trennen müssen, müssen Sie den Destruktor manuell neu und explizit aufrufen. Heutzutage ist dies selten erforderlich, da wir die Standardcontainer haben. Wenn Sie jedoch eine neue Art von Container implementieren müssen, benötigen Sie diese.
quelle
Es gibt Fälle, in denen sie notwendig sind:
In Code, an dem ich arbeite, verwende ich einen expliziten Destruktoraufruf in Allokatoren. Ich habe eine Implementierung eines einfachen Allokators, der die Platzierung new verwendet, um Speicherblöcke an stl-Container zurückzugeben. In zerstören habe ich:
während im Konstrukt:
Die Zuweisung erfolgt auch in allocate () und die Speicherfreigabe in deallocate () unter Verwendung plattformspezifischer Zuweisungs- und Freigabemechanismen. Dieser Allokator wurde verwendet, um Doug Lea Malloc zu umgehen und direkt beispielsweise LocalAlloc unter Windows zu verwenden.
quelle
Ich habe 3 Gelegenheiten gefunden, bei denen ich dies tun musste:
quelle
Ich bin noch nie auf eine Situation gestoßen, in der man einen Destruktor manuell aufrufen muss. Ich scheine mich zu erinnern, dass sogar Stroustrup behauptet, es sei eine schlechte Praxis.
quelle
C+
☺Was ist damit?
Der Destruktor wird nicht aufgerufen, wenn eine Ausnahme vom Konstruktor ausgelöst wird. Daher muss ich ihn manuell aufrufen, um Handles zu zerstören, die vor der Ausnahme im Konstruktor erstellt wurden.
quelle
ctor
hier geschrieben haben, ist falsch, genau aus dem Grund, den Sie selbst angegeben haben: Wenn die Ressourcenzuweisung fehlschlägt, liegt ein Problem bei der Bereinigung vor. Ein 'ctor' sollte nicht anrufenthis->~dtor()
.dtor
sollte für konstruierte Objekte aufgerufen werden, und in diesem Fall ist das Objekt noch nicht konstruiert. Was auch immer passiert, dasctor
sollte die Bereinigung übernehmen. Innerhalb desctor
Codes sollten Sie Utils verwendenstd::unique_ptr
, um die automatische Bereinigung für Sie zu erledigen, wenn etwas ausgelöst wird. Das Ändern vonHANDLE h1, h2
Feldern in der Klasse zur Unterstützung der automatischen Bereinigung könnte ebenfalls eine gute Idee sein.MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }
und das ist es . Keine riskante manuelle Bereinigung, keine Aufbewahrungsgriffe in teilweise konstruierten Objekten, bis alles sicher ist, ist ein Bonus. Wenn SieHANDLE h1,h2
in der Klasse zucleanupGuard<HANDLE> h1;
etc wechseln , brauchen Sie das möglicherweise gar nichtdtor
.cleanupGuard1
undcleanupGuard2
hängt davon ab, was eine relevantexxxToCreate
Rückgabe bewirkt und welche Parameter eine relevante RückgabexxxxToDestroy
übernimmt. Wenn sie einfach sind, müssen Sie möglicherweise gar nichts schreiben, da sich oft herausstellt, dassstd::unique_ptr<x,deleter()>
(oder eine ähnliche) in beiden Fällen den Trick für Sie tun kann.Es wurde ein weiteres Beispiel gefunden, in dem Sie Destruktoren manuell aufrufen müssten. Angenommen, Sie haben eine variantenähnliche Klasse implementiert, die einen von mehreren Datentypen enthält:
Wenn die
Variant
Instanz a enthieltstd::string
und Sie der Union jetzt einen anderen Typ zuweisen, müssen Sie denstd::string
ersten zerstören . Der Compiler macht das nicht automatisch .quelle
Ich habe eine andere Situation, in der ich es für absolut vernünftig halte, den Destruktor anzurufen.
Wenn Sie eine Methode vom Typ "Zurücksetzen" schreiben, um ein Objekt in seinen Ausgangszustand zurückzusetzen, ist es durchaus sinnvoll, den Destruktor aufzurufen, um die alten Daten zu löschen, die zurückgesetzt werden.
quelle
cleanup()
Methode deklarieren würden , die in diesem Fall und im Destruktor aufgerufen werden soll ?