Ist das Löschen erlaubt?

232

Ist es erlaubt, delete this;wenn die delete-Anweisung die letzte Anweisung ist, die auf dieser Instanz der Klasse ausgeführt wird? Natürlich bin ich mir sicher, dass das durch den thisZeiger dargestellte Objekt nur newerstellt wurde.

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?

Martijn Courteaux
quelle
13
Das Hauptproblem besteht darin, dass Sie delete thiseine enge Kopplung zwischen der Klasse und der Zuordnungsmethode erstellt haben, die zum Erstellen von Objekten dieser Klasse verwendet wird. Das ist ein sehr schlechtes OO-Design, da das Grundlegendste in OOP darin besteht, autonome Klassen zu erstellen, die nicht wissen oder sich nicht darum kümmern, was ihr Anrufer tut. Daher sollte eine richtig gestaltete Klasse nicht wissen oder sich darum kümmern, wie sie zugewiesen wurde. Wenn Sie aus irgendeinem Grund einen so besonderen Mechanismus benötigen, wäre es meines Erachtens besser, eine Wrapper-Klasse um die eigentliche Klasse herum zu verwenden und den Wrapper mit der Zuordnung befassen zu lassen.
Lundin
Kannst du nicht löschen setWorkingModule?
Jimmy T.

Antworten:

238

Die C ++ FAQ Lite hat einen speziellen Eintrag dafür

Ich denke, dieses Zitat fasst es gut zusammen

Solange Sie vorsichtig sind, kann ein Objekt Selbstmord begehen (löschen Sie dies).

JaredPar
quelle
15
Die entsprechende FQA hat auch einige nützliche Kommentare: yosefk.com/c++fqa/heap.html#fqa-16.15
Alexandre C.
1
Aus Sicherheitsgründen können Sie einen privaten Destruktor für das ursprüngliche Objekt verwenden, um sicherzustellen, dass es nicht auf dem Stapel oder als Teil eines Arrays oder Vektors erstellt wird.
Cem Kalyoncu
Definieren Sie "vorsichtig"
CinCout
3
'Vorsicht' ist im verlinkten FAQ-Artikel definiert. (Während der FQA-Link meistens schimpft - wie fast alles darin - wie schlecht C ++ ist)
CharonX
88

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 sagt delete this;, 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 deleteder Operand ein Ausdruck sein muss, der einen Zeiger auf ein Objekt oder ein Array von Objekten bezeichnet. Es geht ziemlich detailliert auf Dinge ein, wie es herausfindet, welche (wenn überhaupt) Freigabefunktion aufgerufen werden muss, um den Speicher freizugeben, aber der gesamte Abschnitt über delete(§ [expr.delete]) wird überhaupt nicht delete this;speziell erwähnt. Der Abschnitt über Zerstörer erwähntdelete this an einem Ort (§ [class.dtor] / 13):

Zum Zeitpunkt der Definition eines virtuellen Destruktors (einschließlich einer impliziten Definition (15.8)) wird die Nicht-Array-Freigabefunktion so festgelegt, als ob für den Ausdruck diese in einem nicht virtuellen Destruktor der Destruktorklasse erscheinenden gelöscht werden soll (siehe 8.3.5 ).

Dies stützt tendenziell die Idee, dass der Standard als delete this;gültig erachtet wird - wenn er ungültig wäre, wäre sein Typ nicht aussagekräftig. delete this;Soweit ich weiß, ist dies der einzige Ort, den der Standard überhaupt erwähnt .

Wie auch immer, einige denken über delete thiseinen bösen Hack nach und sagen jedem, der zuhört, dass er vermieden werden sollte. Ein häufig genanntes Problem ist die Schwierigkeit, sicherzustellen, dass Objekte der Klasse immer nur dynamisch zugewiesen werden. Andere halten es für eine absolut vernünftige Redewendung und verwenden es ständig. Persönlich bin ich irgendwo in der Mitte: Ich benutze es selten, aber zögern Sie nicht, es zu tun, wenn es das richtige Werkzeug für den Job zu sein scheint.

Sie verwenden diese Technik hauptsächlich mit einem Objekt, das ein Leben hat, das fast ausschließlich seinem eigenen entspricht. Ein Beispiel, das James Kanze angeführt hat, war ein Abrechnungs- / Nachverfolgungssystem, an dem er für eine Telefongesellschaft gearbeitet hat. Wenn Sie anfangen zu telefonieren, nimmt etwas dies zur Kenntnis und erstellt ein phone_callObjekt. Ab diesem Zeitpunkt verarbeitet das phone_callObjekt die Details des Telefonanrufs (Herstellen einer Verbindung beim Wählen, Hinzufügen eines Eintrags zur Datenbank, um anzuzeigen, wann der Anruf gestartet wurde, möglicherweise Verbinden weiterer Personen, wenn Sie eine Telefonkonferenz führen usw.) Wann phone_callWenn die letzten Personen des Anrufs auflegen, führt das Objekt die endgültige Buchführung durch (z. B. fügt der Datenbank einen Eintrag hinzu, der angibt, wann Sie aufgelegt haben, damit sie berechnen können, wie lange Ihr Anruf gedauert hat), und zerstört sich dann selbst. Die Lebensdauer desphone_callDas Objekt basiert darauf, wann die erste Person den Anruf startet und wann die letzten Personen den Anruf verlassen - aus Sicht des restlichen Systems ist es im Grunde genommen völlig willkürlich, sodass Sie es nicht an einen lexikalischen Bereich im Code binden können oder irgendetwas in dieser Reihenfolge.

Für alle, denen es wichtig sein könnte, wie zuverlässig diese Art der Codierung sein kann: Wenn Sie nach, von oder durch fast jeden Teil Europas telefonieren, besteht eine gute Chance, dass sie (zumindest teilweise) per Code verarbeitet wird das macht genau das.

Jerry Sarg
quelle
2
Danke, ich werde es irgendwo in meiner Erinnerung behalten. Ich nehme an, Sie definieren die Konstruktoren und Destruktoren als privat und verwenden eine statische Factory-Methode, um solche Objekte zu erstellen.
Alexandre C.
@Alexandre: Das würden Sie wahrscheinlich sowieso in den meisten Fällen tun - ich weiß nicht annähernd alle Details des Systems, an dem er gearbeitet hat, daher kann ich nicht sicher sagen.
Jerry Coffin
Die Art und Weise, wie ich das Problem der Speicherzuweisung häufig umgehen kann, besteht darin, einen bool selfDeleteParameter in den Konstruktor aufzunehmen, der einer Mitgliedsvariablen zugewiesen wird. Zugegeben, dies bedeutet, dem Programmierer genug Seil zu geben, um eine Schlinge darin zu binden, aber ich finde, dass dies Speicherlecks vorzuziehen ist.
MBraedley
1
@ MBraedley: Ich habe das Gleiche getan, aber lieber vermieden, was mir wie ein Kludge erscheint.
Jerry Coffin
Für alle, die sich interessieren könnten ... gibt es eine ziemlich gute Chance, dass es (zumindest teilweise) von Code gehandhabt wird, der genau das tut this. Ja, der Code wird von der genau behandelt this. ;)
Galaxy
46

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

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

Ich denke, es delete thisist idiomatisches 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, die Mitgliedsdaten vom Objekt benötigt. 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 älter als C ++ 11 Sie verwenden können , std::auto_ptrstatt std::unique_ptr, wird es das gleiche tun.

Mark Ransom
quelle
Ich kann dies nicht mit c ++ 11 kompilieren. Gibt es spezielle Compileroptionen dafür? Benötigt es auch keine Bewegung dieses Zeigers?
Eule
@Owl nicht sicher, was du meinst, es funktioniert für mich: ideone.com/aavQUK . Das Erstellen eines unique_ptrvon einem anderen unique_ptr erfordert einen Zug, jedoch nicht von einem rohen Zeiger. Es sei denn, die Dinge haben sich in C ++ 17 geändert?
Mark Ransom
Ahh C ++ 14, deshalb. Ich muss mein C ++ auf meiner Entwicklungsbox aktualisieren. Ich werde es heute Abend noch einmal mit meinem kürzlich entstandenen Gentoo-System versuchen!
Eule
25

Einer der Gründe, warum C ++ entwickelt wurde, war die einfache Wiederverwendung von Code. Im Allgemeinen sollte C ++ so geschrieben werden, dass es funktioniert, unabhängig davon, ob die Klasse auf dem Heap, in einem Array oder auf dem Stapel 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 besser keine weitere Löschanweisung geben, die normalerweise von den meisten Entwicklern verwendet wird, um den Heap zu bereinigen. Dabei wird auch davon ausgegangen, dass in Zukunft kein Wartungsprogrammierer einen falsch wahrgenommenen Speicherverlust durch Hinzufügen einer Löschanweisung beheben kann.

Selbst wenn Sie im Voraus wissen, dass Ihr aktueller Plan darin besteht, nur eine einzige Instanz auf dem Heap zuzuweisen, was ist, wenn in Zukunft ein Happy-Go-Lucky-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 "dies löschen" erreicht, wird er gelöscht und gelöscht. Wenn das Objekt jedoch den Gültigkeitsbereich verlässt, wird der Destruktor aufgerufen. Der Destruktor versucht dann erneut, es zu löschen, und dann werden Sie abgespritzt. In der Vergangenheit musste so etwas nicht nur das Programm, sondern auch das Betriebssystem und den Computer neu starten. In jedem Fall wird dies NICHT empfohlen und sollte fast immer vermieden werden. Ich müsste verzweifelt sein, ernsthaft verputzt,

Bob Bryan
quelle
7
+1. Ich kann nicht verstehen, warum du herabgestimmt wurdest. "C ++ sollte so geschrieben sein, dass es funktioniert, ob die Klasse auf dem Heap, in einem Array oder auf dem Stapel instanziiert wird" ist ein sehr guter Rat.
Joh
1
Sie können das Objekt, das Sie selbst löschen möchten, einfach in eine spezielle Klasse einschließen, die das Objekt und dann sich selbst löscht, und diese Technik verwenden, um die Stapelzuweisung zu verhindern: stackoverflow.com/questions/124880/… Es gibt Zeiten, in denen es wirklich keine gibt praktikable Alternative. Ich habe diese Technik nur verwendet, um einen Thread, der von einer DLL-Funktion gestartet wird, selbst zu löschen, aber die DLL-Funktion muss zurückkehren, bevor der Thread endet.
Felix Dombek
Sie können nicht so programmieren, dass jemand, der nur Ihren Code kopiert und einfügt, ihn trotzdem missbraucht
Jimmy T.
22

Es ist erlaubt (benutze das Objekt danach einfach nicht mehr), aber ich würde solchen Code in der Praxis nicht schreiben. Ich denke, das delete thissollte nur in Funktionen erscheinen, die aufgerufen haben releaseoder Releaseund so aussehen : void release() { ref--; if (ref<1) delete this; }.

Kirill V. Lyadvinsky
quelle
Welches ist genau einmal in jedem meiner Projekte ... :-)
cmaster - wieder einsetzen Monica
15

Nun, in COM (Component Object Model) delete thiskann die Konstruktion ein Teil der ReleaseMethode sein, die aufgerufen wird, wenn Sie ein gewünschtes Objekt freigeben möchten:

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

Dies ist die Kernsprache für Objekte mit Referenzzählung.

Das Referenzzählen ist eine starke Form der deterministischen Speicherbereinigung. Es 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 intelligente "Referenz" -Zeiger zugegriffen, die so ausgelegt sind, dass die Zeiger eine Element-Ganzzahl (den Referenzzähler) im tatsächlichen Objekt inkrementieren und dekrementieren.

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" zur Speicherbereinigung. Die von mir geschriebenen Bibliotheken bieten einen geschützten virtuellen "CountIsZero" -Aufruf in der Basisklasse, damit Sie dieses Verhalten für Dinge wie Caching überschreiben können.

Der Schlüssel, um dies sicher zu machen, besteht nicht darin, Benutzern den Zugriff auf den CONSTRUCTOR des betreffenden Objekts zu ermöglichen (es 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 verfügbar ist, sodass "dies löschen" niemals explodieren wird.

Zack Yezek
quelle
Warum können Sie nicht einfach eine (Singleton-) Klasse "Allocator / Garbage Collector" haben, eine Schnittstelle, über die die gesamte Zuordnung erfolgt, und diese Klasse die gesamte Referenzzählung der zugewiesenen Objekte durchführen lassen? Anstatt die Objekte selbst zu zwingen, sich mit Speicherbereinigungsaufgaben zu beschäftigen, die völlig unabhängig von ihrem festgelegten Zweck sind.
Lundin
1
Sie können den Destruktor auch einfach schützen, um statische Zuordnungen und Stapelzuordnungen Ihres Objekts zu verhindern.
Game_Overture
7

Sie können dies tun. Sie können dies jedoch nicht zuordnen. Der Grund, warum Sie dies angeben: "Ich möchte die Ansicht ändern", erscheint daher sehr fraglich. Die bessere Methode wäre meiner Meinung nach, dass das Objekt, das die Ansicht enthält, diese Ansicht ersetzt.

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

Edward Strange
quelle
4

Dies ist eine alte, beantwortete Frage, aber @Alexandre fragte: "Warum sollte jemand dies tun wollen?", Und ich dachte, ich könnte eine Beispielverwendung bereitstellen, 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 viel Code zu ändern, wenn ich ihn ref_cnt_ptr<Obj>überall 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 explizites_delete_ref_cnt_ptr zu erstellen. Dh ein Referenzzählzeiger, 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 Inkrementieren und Dekrementieren des Referenzzählers als explizit_delete_ref_cnt_ptr wird manipuliert.

Aber NICHT freigeben, wenn der Referenzzähler im Destruktor explic_delete_ref_cnt_ptr Null ist.

Nur freigeben, wenn der Referenzzähler in einer expliziten löschähnlichen Operation als Null angezeigt wird. ZB in so 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 etwas. 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, als würde dies das Mischen von nackten Zeigern und rc'ed-Zeigern etwas sicherer machen.

Bisher ist dies jedoch nicht erforderlich.

Aber dann kam mir der Gedanke: Wenn das Objekt, auf das der Pointee zeigt, weiß, dass es als Referenz gezählt wird, z. B. wenn sich die Zählung innerhalb des Objekts (oder in einer anderen Tabelle) befindet, könnte die Routine delete_if_rc0 eine Methode der sein Pointee-Objekt, nicht der (intelligente) Zeiger.

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

Eigentlich muss es überhaupt keine Mitgliedsmethode sein, könnte aber 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.)

Krazy Glew
quelle
0

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

Swift - Friday Pie
quelle