wake-up-neo.com

Ist das Löschen erlaubt?

Darf ich delete this; Wenn die delete-Anweisung die letzte Anweisung ist, die für diese Instanz der Klasse ausgeführt wird? Natürlich bin ich mir sicher, dass das durch den Zeiger this- dargestellte Objekt newly-created ist.

Ich denke über so etwas nach:

void SomeModule::doStuff()
{
    // in the controller, "this" object of SomeModule is the "current module"
    // now, if I want to switch over to a new Module, eg:

    controller->setWorkingModule(new OtherModule());

    // since the new "OtherModule" object will take the lead, 
    // I want to get rid of this "SomeModule" object:

    delete this;
}

Kann ich das tun?

217

Das C++ FAQ Lite hat speziell dafür einen Eintrag

Ich denke, dieses Zitat fasst es gut zusammen

Solange Sie vorsichtig sind, ist es in Ordnung, dass ein Objekt Selbstmord begeht (löschen Sie dies).

225
JaredPar

Ja, delete this; Hat Ergebnisse definiert, solange Sie (wie Sie bemerkt haben) sicherstellen, dass das Objekt dynamisch zugewiesen wurde, und (natürlich) niemals versuchen, das Objekt zu verwenden, nachdem es zerstört wurde. Im Laufe der Jahre wurden viele Fragen dazu gestellt, was der Standard speziell über delete this; Sagt, anstatt einen anderen Zeiger zu löschen. Die Antwort darauf ist ziemlich kurz und einfach: Sie sagt nicht viel aus. Es heißt nur, dass der Operand von delete ein Ausdruck sein muss, der einen Zeiger auf ein Objekt oder ein Array von Objekten bezeichnet. Es wird ziemlich detailliert darauf eingegangen, wie herausgefunden wird, welche (wenn überhaupt) Freigabe-Funktion aufgerufen werden muss, um den Speicher freizugeben, aber der gesamte Abschnitt über delete (§ [expr.delete]) funktioniert nicht. t erwähne delete this; überhaupt nicht spezifisch. Der Abschnitt über Destruktoren erwähnt delete this An einer Stelle (§ [class.dtor]/13):

An der Definitionsstelle eines virtuellen Destruktors (einschließlich einer impliziten Definition (15.8)) wird die Nicht-Array-Freigabefunktion so festgelegt, als ob für den Ausdruck delete diese in einem nicht-virtuellen Destruktor der Destruktorklasse erscheinen würde (siehe 8.3.5.) ).

Dies spricht eher für die Annahme, dass der Standard delete this; Für gültig hält - wenn er ungültig wäre, wäre sein Typ nicht aussagekräftig. Soweit ich weiß, ist dies der einzige Ort, den der Standard überhaupt erwähnt delete this;.

Wie auch immer, manche halten delete this Für einen üblen Hack und sagen jedem, der zuhört, dass dies vermieden werden sollte. Ein häufig genanntes Problem ist die Schwierigkeit, sicherzustellen, dass Objekte der Klasse immer nur dynamisch zugeordnet werden. Andere halten es für eine völlig vernünftige Redewendung und verwenden es die ganze Zeit. Persönlich bin ich irgendwo in der Mitte: Ich benutze es selten, aber zögere nicht, es zu tun, wenn es das richtige Werkzeug für den Job zu sein scheint.

Die primäre Zeit, die Sie diese Technik verwenden, ist mit einem Objekt, das ein Leben hat, das fast sein ganzes eigen ist. Ein Beispiel, das James Kanze angeführt hat, war ein Abrechnungs-/Verfolgungssystem, an dem er für eine Telefongesellschaft gearbeitet hat. Wenn Sie einen Anruf tätigen, nimmt etwas davon Kenntnis und erstellt ein phone_call - Objekt. Ab diesem Zeitpunkt verwaltet das Objekt phone_call Die Details des Telefonanrufs (Herstellen einer Verbindung beim Wählen, Hinzufügen eines Eintrags zur Datenbank, um anzugeben, wann der Anruf gestartet wurde, und möglicherweise Verbinden von mehr Personen, wenn Sie eine Konferenz durchführen Anruf usw.) Wenn die letzten Teilnehmer des Anrufs auflegen, führt das Objekt phone_call seine endgültige Buchhaltung durch (z. B. fügt der Datenbank einen Eintrag hinzu, der angibt, wann Sie aufgelegt haben, damit sie berechnen können, wie lang war dein anruf) und zerstört sich dann selber. Die Lebensdauer des Objekts phone_call Hängt davon ab, wann die erste Person den Anruf startet und wann die letzten Personen den Anruf beenden - aus Sicht des restlichen Systems ist dies im Grunde genommen völlig willkürlich. kann nicht es an einen lexikalischen Bereich im Code oder irgendetwas in dieser Reihenfolge binden.

Für alle, denen es wichtig ist, wie zuverlässig diese Art der Codierung sein kann: Wenn Sie in, aus oder durch fast jeden Teil Europas telefonieren, besteht eine gute Chance, dass die Codierung (zumindest teilweise) erfolgt das macht genau das.

80
Jerry Coffin

Wenn es dir Angst macht, gibt es einen vollkommen legalen Hack:

void myclass::delete_me()
{
    std::unique_ptr<myclass> bye_bye(this);
}

Ich glaube delete this ist jedoch idiomatisch C++, und ich präsentiere dies nur als eine Kuriosität.

Es gibt einen Fall, in dem dieses Konstrukt tatsächlich nützlich ist - Sie können das Objekt löschen, nachdem Sie eine Ausnahme ausgelöst haben, für die Mitgliedsdaten vom Objekt benötigt werden. Das Objekt bleibt bis nach dem Wurf gültig.

void myclass::throw_error()
{
    std::unique_ptr<myclass> bye_bye(this);
    throw std::runtime_exception(this->error_msg);
}

Hinweis: Wenn Sie einen Compiler verwenden, der älter als C++ 11 ist, können Sie std::auto_ptr anstatt std::unique_ptr, es wird dasselbe tun.

45
Mark Ransom

Einer der Gründe, warum C++ entwickelt wurde, bestand darin, die Wiederverwendung von Code zu vereinfachen. Im Allgemeinen sollte C++ so geschrieben werden, dass es funktioniert, ob die Klasse auf dem Heap, in einem Array oder auf dem Stack instanziiert wird. "Delete this" ist eine sehr schlechte Codierungspraxis, da es nur funktioniert, wenn eine einzelne Instanz auf dem Heap definiert ist. und es sollte keine weitere delete-Anweisung geben, die normalerweise von den meisten Entwicklern verwendet wird, um den Heap aufzuräumen. Dabei wird auch davon ausgegangen, dass kein Wartungsprogrammierer in Zukunft einen fälschlicherweise wahrgenommenen Speicherverlust durch Hinzufügen einer delete-Anweisung beheben kann.

Auch wenn Sie im Voraus wissen, dass Sie derzeit nur eine Instanz auf dem Heap zuweisen möchten, was ist, wenn in Zukunft ein glücklicher Entwickler hinzukommt und beschließt, eine Instanz auf dem Stack zu erstellen? Oder was ist, wenn er bestimmte Teile der Klasse ausschneidet und in eine neue Klasse einfügt, die er auf dem Stapel verwenden möchte? Wenn der Code "delete this" erreicht, wird er deaktiviert und gelöscht. Wenn das Objekt den Gültigkeitsbereich verlässt, wird der Destruktor aufgerufen. Der Destruktor wird dann versuchen, es wieder zu löschen und dann werden Sie abgespritzt. In der Vergangenheit mussten bei einer solchen Aktion nicht nur das Programm, sondern auch das Betriebssystem und der Computer neu gestartet werden. In jedem Fall wird dies NICHT empfohlen und sollte fast immer vermieden werden. Ich müsste verzweifelt sein, ernsthaft verputzt oder die Firma, für die ich gearbeitet habe, wirklich hassen, um Code zu schreiben, der dies tat.

22
Bob Bryan

Es ist erlaubt (benutze das Objekt danach einfach nicht mehr), aber ich würde in der Praxis keinen solchen Code schreiben. Ich denke, dass delete this Nur in Funktionen erscheinen sollte, die release oder Release aufgerufen haben und wie folgt aussehen: void release() { ref--; if (ref<1) delete this; }.

19

Nun, in Component Object Model (COM) delete this Die Konstruktion kann Teil der Release -Methode sein, die aufgerufen wird, wenn Sie ein bestimmtes Objekt freigeben möchten:

void IMyInterface::Release()
{
    --instanceCount;
    if(instanceCount == 0)
        delete this;
}
13
UnknownGosu

Dies ist die Kernsprache für referenzzählende Objekte.

Referenzzählung ist eine starke Form der deterministischen Speicherbereinigung. Sie stellt sicher, dass Objekte ihre EIGENE Lebensdauer verwalten, anstatt sich auf "intelligente" Zeiger usw. zu verlassen, um dies für sie zu tun. Auf das zugrunde liegende Objekt wird immer nur über "Referenz" -Smartpointer zugegriffen, die so konzipiert sind, dass die Pointer eine Member-Ganzzahl (den Referenzzähler) im tatsächlichen Objekt erhöhen und verringern.

Wenn die letzte Referenz vom Stapel fällt oder gelöscht wird, geht der Referenzzähler auf Null. Das Standardverhalten Ihres Objekts ist dann ein Aufruf zum "Löschen" der Garbage-Collect-Funktion. Die von mir geschriebenen Bibliotheken stellen einen geschützten virtuellen "CountIsZero" -Aufruf in der Basisklasse bereit, damit Sie dieses Verhalten für Dinge wie Caching außer Kraft setzen können.

Der Schlüssel, um diese Sicherheit zu gewährleisten, besteht darin, den Benutzern keinen Zugriff auf den CONSTRUCTOR des betreffenden Objekts zu gewähren (ihn geschützt zu machen), sondern sie dazu zu bringen, ein statisches Mitglied - das FACTORY-ähnliche "static Reference CreateT (...)" - aufzurufen. Auf diese Weise wissen Sie sicher, dass sie immer mit gewöhnlichen "neuen" erstellt werden und dass kein roher Zeiger jemals verfügbar ist, so dass "dies löschen" niemals explodieren wird.

8
Zack Yezek

Sie können das tun. Sie können dies jedoch nicht zuordnen. Der Grund, den Sie dafür angeben, "Ich möchte die Sichtweise ändern", erscheint daher sehr fraglich. Meiner Meinung nach wäre die bessere Methode, wenn das Objekt, das die Ansicht enthält, diese Ansicht ersetzt.

Natürlich verwenden Sie RAII-Objekte und müssen deshalb eigentlich gar nicht delete aufrufen ... oder?

7
Edward Strange

Dies ist eine alte, beantwortete Frage, aber @Alexandre fragte: "Warum sollte jemand dies tun?", Und ich dachte, ich könnte ein Beispiel für eine Verwendung nennen, die ich heute Nachmittag in Betracht ziehe.

Legacy-Code. Verwendet nackte Zeiger Obj * obj mit einem Löschobjekt am Ende.

Leider muss ich manchmal, nicht oft, das Objekt länger am Leben halten.

Ich denke darüber nach, es zu einem intelligenten Zeiger mit Referenzzählung zu machen. Aber es gäbe viele Code zu ändern, wenn ich überall ref_cnt_ptr<Obj> Verwenden würde. Und wenn Sie nacktes Obj * und ref_cnt_ptr mischen, können Sie das Objekt implizit löschen lassen, wenn das letzte ref_cnt_ptr verschwindet, obwohl Obj * noch am Leben ist.

Ich denke also darüber nach, ein explicit_delete_ref_cnt_ptr zu erstellen. Das heißt Ein Zeiger mit Referenzzählung, bei dem das Löschen nur in einer expliziten Löschroutine erfolgt. Verwenden Sie es an einer Stelle, an der der vorhandene Code die Lebensdauer des Objekts kennt, sowie in meinem neuen Code, der das Objekt länger am Leben hält.

Das Erhöhen und Verringern der Referenzanzahl als explicit_delete_ref_cnt_ptr wird manipuliert.

Wird jedoch NICHT freigegeben, wenn der Referenzzähler im Destruktor explicit_delete_ref_cnt_ptr als Null angezeigt wird.

Wird nur freigegeben, wenn bei einer expliziten löschähnlichen Operation festgestellt wird, dass der Referenzzähler Null ist. Z.B. in etwas wie:

template<typename T> class explicit_delete_ref_cnt_ptr { 
 private: 
   T* ptr;
   int rc;
   ...
 public: 
   void delete_if_rc0() {
      if( this->ptr ) {
        this->rc--;
        if( this->rc == 0 ) {
           delete this->ptr;
        }
        this->ptr = 0;
      }
    }
 };

OK, so ähnlich. Es ist etwas ungewöhnlich, dass ein Zeigertyp mit Referenzzählung das Objekt, auf das im rc'ed ptr-Destruktor verwiesen wird, nicht automatisch löscht. Aber es scheint so, als würde das Mischen von nackten Zeigern und entfernten Zeigern ein bisschen sicherer werden.

Bisher ist es jedoch nicht erforderlich, dies zu löschen.

Aber dann kam mir der Gedanke: Wenn das Objekt, auf das gezeigt wird, der Spitzenreiter weiß, dass es als Referenz gezählt wird, z. Befindet sich die Anzahl innerhalb des Objekts (oder in einer anderen Tabelle), kann die Routine delete_if_rc0 eine Methode des Pointee-Objekts und nicht des (intelligenten) Zeigers sein.

class Pointee { 
 private: 
   int rc;
   ...
 public: 
   void delete_if_rc0() {
        this->rc--;
        if( this->rc == 0 ) {
           delete this;
        }
      }
    }
 };

Eigentlich muss es keine Member-Methode sein, sondern kann eine freie Funktion sein:

map<void*,int> keepalive_map;
template<typename T>
void delete_if_rc0(T*ptr) {
        void* tptr = (void*)ptr;
        if( keepalive_map[tptr] == 1 ) {
           delete ptr;
        }
};

(Übrigens, ich weiß, dass der Code nicht ganz richtig ist - er wird weniger lesbar, wenn ich alle Details hinzufüge, also lasse ich ihn so.)

4
Krazy Glew

Löschen Sie dies ist legal, solange sich das Objekt im Heap befindet. Sie müssten verlangen, dass das Objekt nur Heap ist. Die einzige Möglichkeit, dies zu tun, besteht darin, den Destruktor geschützt zu machen. Auf diese Weise kann delete NUR aus der Klasse aufgerufen werden, sodass Sie eine Methode benötigen, die das Löschen sicherstellt

0