Angenommen, es gibt ein Objekt A, das über std::unique_ptr<B>
ein Objekt B besitzt. Weiter B enthält einen (schwachen) Verweis auf einen rohen Zeiger auf A. Dann ruft der Destruktor von A den Destruktor von B auf, da er ihn besitzt.
Was wird ein sicherer Weg sein, um A im Destruktor von B zu erreichen? (da wir vielleicht auch im Destruktor von A sind).
Ein sicherer Weg, den starken Bezug auf B im Destruktor von A explizit zurückzusetzen, so dass B auf vorhersehbare Weise zerstört wird, aber was ist die allgemein beste Vorgehensweise?
Ich bin kein Sprachanwalt, aber ich denke, es ist in Ordnung. Sie treten auf gefährlichen Boden und sollten vielleicht Ihr Design überdenken, aber wenn Sie vorsichtig sind, denke ich, können Sie sich einfach auf die Tatsache verlassen, dass Mitglieder werden in der umgekehrten Reihenfolge zerstört, in der sie deklariert wurden .
Also das ist OK
%Vor%, da die Mitglieder von A
in der Reihenfolge n2
, dann b
und dann n1
zerstört werden. Aber das ist nicht in Ordnung
seit n2
wurde bereits zerstört, bis B
versucht, es zu benutzen.
Was wird ein sicherer Weg sein, um A im Destruktor von B zu erreichen? (da wir vielleicht auch im Destruktor von A sind).
Es gibt keinen sicheren Weg :
3.8 / 1
[...] Die Lebensdauer eines Objekts vom Typ T endet, wenn:
- Wenn T ein Klassentyp mit einem nicht-trivialen Destruktor (12.4) ist, startet der Destruktoraufruf [...]
Ich denke, es ist einfach, dass Sie nach Ablauf der Lebenszeit nicht mehr auf das Objekt zugreifen können.
BEARBEITEN: Als Chris Drew im Kommentar schrieb, kann das Objekt benutzen, nachdem sein Destruktor gestartet wurde, mein Fehler, ich habe einen wichtigen Satz im Standard verpasst:
3.8 / 5
Vor der Lebensdauer eines Objekts hat es begonnen, aber nach dem Speicher, den das Objekt belegen wird allocated oder, nachdem die Lebensdauer eines Objekts abgelaufen ist und vor dem Speicher, den das Objekt belegt hat wiederverwendet oder freigegeben, jeder Zeiger, der auf den Speicherort verweist, an dem sich das Objekt befindet oder befand kann aber nur begrenzt verwendet werden. Für ein Objekt im Aufbau oder eine Zerstörung, siehe 12.7 . Andernfalls, Ein solcher Zeiger verweist auf den zugewiesenen Speicher (3.7.4.2) und verwendet den Zeiger, als ob der Zeiger vom Typ void * wäre. ist gut definiert. Ein solcher Zeiger kann dereferenziert werden, aber der resultierende L-Wert kann nur begrenzt verwendet werden Wege, wie unten beschrieben. Das Programm hat undefiniertes Verhalten, wenn: [...]
In 12.7 gibt es eine Liste von Dingen, die Sie während des Baus und der Zerstörung tun können, einige der wichtigsten:
12.7 / 3:
Um explizit oder implizit einen Zeiger (einen glvalue) zu konvertieren, verweist auf ein Objekt der Klasse X auf einen Zeiger (Referenz) zu eine direkte oder indirekte Basisklasse B von X , die Konstruktion von X und die Konstruktion aller seiner direkten oder Indirekte Grundlagen, die direkt oder indirekt von B herrühren, müssen begonnen haben und die
dieser Klassen darf nicht abgeschlossen sein, andernfalls führt die Konvertierung zu undefiniertem Verhalten. Um einen Zeiger auf (oder Zugriff auf den Wert von) ein direktes nicht-statisches Element eines Objekts obj , die Konstruktion von obj soll begonnen haben und seine Zerstörung darf nicht abgeschlossen sein , andernfalls die Berechnung des Zeigerwertes (oder des Zugriffs auf der Member-Wert) führt zu nicht definiertem Verhalten.
12.7 / 4
Elementfunktionen , einschließlich virtueller Funktionen (10.3), können während der Konstruktion oder Zerstörung (12.6.2) aufgerufen werden. Wenn eine virtuelle Funktion direkt oder indirekt von einem Konstruktor oder von einem Destruktor aufgerufen wird, einschließlich während der Konstruktion oder Zerstörung der nicht-statischen Datenelemente der Klasse, und das Objekt, zu dem die call apply ist das Objekt (call it x) im Aufbau oder Zerstörung, die aufgerufene Funktion ist der final overrider in der Klasse des Konstruktors oder des Destruktors und nicht in einer Klasse mit mehr abgeleiteten Klassen. Wenn das virtuelle Funktionsaufruf verwendet einen expliziten Klassenmemberzugriff (5.2.5) und der Objektausdruck bezieht sich auf den vollständigen Objekt von x oder eines der Basisklassenunterobjekte dieses Objekts, aber nicht x oder eines seiner Basisklassenunterobjekte, das Verhalten ist nicht definiert.
Wie bereits erwähnt, gibt es keinen "sicheren Weg". Wie von PcAF aufgezeigt wurde, ist die Lebensdauer von A
bereits zu dem Zeitpunkt, zu dem Sie% des_des% destructor erreichen, beendet
Ich möchte nur darauf hinweisen, dass dies tatsächlich eine gute Sache ist! Es muss eine strenge Reihenfolge geben, in der Objekte zerstört werden.
Nun, was Sie tun sollten, ist B
vorher zu sagen , dass B
zerstört werden soll.
Es ist so einfach wie
Das übergeben des A
Poiners kann notwendig sein oder nicht, abhängig vom Design ob this
(Wenn B
viele Referenzen enthält, muss es möglicherweise wissen welche zu lösen ist. Wenn es nur eins enthält, ist B
Zeiger ist überflüssig).
Stellen Sie nur sicher, dass die entsprechenden Member-Funktionen privat sind, so dass die Schnittstelle nur in der beabsichtigten Weise verwendet werden kann.
Anmerkung :
Dies ist eine einfache, leichtgewichtige Lösung, die in Ordnung ist, wenn Sie selbst die Kommunikation zwischen this
und A
vollständig steuern. Nicht sollte dies unter keinen Umständen als Netzwerkprotokoll definieren! Das erfordert viel mehr Sicherheitszäune.
Bedenken Sie Folgendes:
%Vor% "Dann wird der Destruktor von A den Destruktor von B aufrufen, da er ihn besitzt." Dies ist nicht die richtige Methode zum Aufruf von Destruktoren im Falle von Composite Objekte. Wenn Sie das obige Beispiel sehen, wird zuerst a
zerstört und dann b
zerstört. Der Destruktor von a
ruft% destructor b
nicht auf, so dass das Steuerelement zurück zu% destructor a
wird.
"Was ist eine sichere Methode, um im Destruktor von B auf A zuzugreifen?" . Wie im obigen Beispiel ist a
bereits zerstört, daher kann a
nicht in% destructor b
aufgerufen werden.
"weil wir vielleicht auch im Destruktor von A sind." . Das ist nicht richtig. Auch wenn das Steuerelement% des Destruktors von a
verlässt, gibt nur die Steuerung den Destruktor b
ein.
Destruktor ist eine Memberfunktion einer Klasse T. Sobald das Steuerelement den Destruktor verlässt, kann nicht auf die Klasse T zugegriffen werden. Auf alle Datenelemente der Klasse T kann in Konstruktoren und Destruktoren der Klasse T wie im obigen Beispiel zugegriffen werden.
Wenn Sie nur auf die Beziehungen der beiden Klassen A und B schauen, ist die Konstruktion gut:
%Vor%Die Probleme entstehen, wenn Objekte von A und B auf andere Programmeinheiten übertragen werden. Dann sollten Sie die Tools von std :: memory wie std :: shared_ptr oder std: weak_ptr.
verwendenTags und Links c++ smart-pointers ownership-semantics