Muss enable_shared_from_this die erste Basisklasse sein?

8

Meine Klasse erbt von mehreren Basen, von denen eine ist std::enable_shared_from_this. Muss es die erste Basis sein?

Angenommen, der folgende Beispielcode:

struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};

std::make_shared<C>(); 

Wenn ~A()und ~B()laufe, kann ich sicher sein , dass der Speicher , in dem Cnoch vorhanden gelebt ist?

Filipp
quelle
1
Warum ist Ihnen die Reihenfolge der Zerstörung wichtig? Der Destruktor von std::enable_shared_from_thismacht nicht viel. Ihr Beispiel sieht nicht gut aus für mich (vorausgesetzt , Sie nicht versuchen , etwas zu tun klug in ~Aund ~B, wie unten Guss thiszu C*)
Igor Tandetnik
1
@SM Dies ist kein Zugriffsproblem. Ich weiß, enable_shared_from_thismuss eine zugängliche, eindeutige Basis sein. In meinem Beispiel ist es. Cist eine Struktur. Es erbt öffentlich.
Filipp
1
Ja, aber der Basiszugriff wird durch das Objekt bestimmt, das die Vererbung ausführt, und nicht durch das Objekt, von dem geerbt wird. Ich kann mein Beispiel ändern, wenn Sie wollen. Der tatsächliche Code, auf dem es basiert, verwendet classund public. Ich habe mich structfür das Beispiel entschieden, um das Tippen zu vermeiden.
Filipp
4
So sprach The Standard: " [util.smartptr.weak.dest] ~weak_ptr(); Effekte: Zerstört dieses weak_ptrObjekt, hat jedoch keine Auswirkungen auf das Objekt, auf das sein gespeicherter Zeiger zeigt." Hervorhebung von mir.
Igor Tandetnik
1
@Filipp Die Lebensdauer des gespeicherten Objekts endet mit dem letzten shared_ptrTod. Selbst wenn das verhindert, dass weak_ptrder Kontrollblock freigegeben wird, denke ich nicht, dass es wichtig ist.
HolyBlackCat

Antworten:

1

Wenn ~A()und ~B()laufe, kann ich sicher sein , dass der Speicher , in dem Cnoch vorhanden gelebt ist?

Na sicher! Es wäre schwierig, eine Basisklasse zu verwenden, die versucht, ihren eigenen Speicher (den Speicher, in dem sie sich befindet) freizugeben. Ich bin mir nicht sicher, ob es überhaupt formal legal ist.

Implementierungen tun dies nicht: Wenn a shared_ptr<T>zerstört oder zurückgesetzt wird, wird der Referenzzähler (RC) für den gemeinsamen Besitz von T(atomar) dekrementiert; Wenn es im Dekrement 0 erreicht hat, wird die Zerstörung / Löschung von Tgestartet.

Dann wird die Anzahl der schwachen Eigentümer oder T-existierenden (atomar) dekrementiert, da sie Tnicht mehr existiert: Wir müssen wissen, ob wir die letzte Entität sind, die an dem Kontrollblock interessiert ist; Wenn die Dekrementierung ein Ergebnis ungleich Null ergab, bedeutet dies weak_ptr, dass einige vorhanden sind (möglicherweise 1 Anteil oder 100%), die Eigentümer des Kontrollblocks sind, und sie sind jetzt für die Freigabe verantwortlich.

In beiden Fällen wird die atomare Dekrementierung für den letzten Miteigentümer irgendwann zu einem Wert von Null führen.

Hier gibt es keine Fäden, keinen Nichtdeterminismus, und offensichtlich wurde der letzte weak_ptr<T>während der Zerstörung von zerstört C. (Die ungeschriebene Annahme in Ihrer Frage ist, dass kein anderer weak_ptr<T>beibehalten wurde.)

Zerstörung geschieht immer in genau dieser Reihenfolge. Der Steuerblock wird zur Zerstörung verwendet, da shared_ptr<T>(im Allgemeinen) nicht bekannt ist, welcher (möglicherweise nicht virtuelle) Destruktor der (möglicherweise unterschiedlichen) am meisten abgeleiteten Klasse aufgerufen werden soll . (Der Steuerblock weiß auch, dass der Speicher nicht freigegeben werden soll, wenn die gemeinsame Anzahl Null erreicht make_shared.)

Die einzige praktische Variation zwischen den Implementierungen scheint in den feinen Details von Speicherzäunen und der Vermeidung einiger atomarer Operationen in häufigen Fällen zu bestehen.

Neugieriger
quelle
Dies ist die Antwort, nach der ich gesucht habe! Vielen Dank! Der Schlüssel ist, dass das lebende Objekt tatsächlich als eine implizite schwache Verwendung zählt. Das ist weak_count1 für ein Objekt, das make_shared-ed wurde, auch wenn es kein weak_ptrs gibt. Nur die shared_ptrersten Dekremente freigeben use_count. Wenn es 0 wird, wird das Objekt (aber nicht der Steuerblock) zerstört. Dann weak_count wird dekrementiert und wenn 0, wird der Steuerblock zerstört + freigegeben. Ein Objekt, das von erbt, enable_shared_from_thisbeginnt mit weak_count= 2. Eine brillante Lösung von STL-Implementierern, wie erwartet.
Filipp
Nur ein pedantischer Trottel: STL ist die Standardvorlagenbibliothek, die mit Ausnahme historischer Artefakte (HP STL oder SGI STL) nur informell definiert ist. Es geht um Typen, die den Anforderungen von Containern, Iteratoren und den daran arbeitenden "Algorithmen" entsprechen. Die STL ist nicht streng auf Vorlagen beschränkt, da einige Nicht-Vorlagenklassen verwendet werden (z random_access_iterator_tag. B. ). Es besteht eine informelle Vereinbarung, alles, was mit Containern zu tun hat, als Teil der STL zu bezeichnen. tl; dr: Nicht alle Vorlagen in der Standardbibliothek sind Teil der STL und nicht alle Nicht-Vorlagen befinden sich außerhalb der STL.
Neugieriger
5

Kann ich beim Ausführen von ~ A () und ~ B () sicher sein, dass der Speicher, in dem C lebte, noch vorhanden ist?

Nein, und die Reihenfolge der Basisklassen ist irrelevant. Sogar die Verwendung (oder nicht) von enable_shared_from_this ist irrelevant.

Wenn ein C-Objekt zerstört wird (was auch immer passiert), ~C()wird es vor beiden aufgerufen, ~A()und ~B()so funktionieren Basis-Destruktoren. Wenn Sie versuchen, das C-Objekt in einem der Basisdestruktoren und Zugriffsfelder darin zu "rekonstruieren", wurden diese Felder bereits zerstört, sodass Sie ein undefiniertes Verhalten erhalten.

Chris Dodd
quelle
Beantwortet meine Frage nicht. Nirgendwo erwähne ich den Versuch, ein C zu "rekonstruieren". Die Antwort sollte eine der folgenden sein: " enable_shared_from_thisKann an einer beliebigen Stelle in der Basisliste erscheinen. Implementierungen sind erforderlich, um nach der Zerstörung des gesamten Objekts Speicher freizugeben, unabhängig davon, wie es von enable_shared_from_this" oder "It" erbt muss die erste Basis sein, erbt irgendwo anders UB "oder" Dieses Verhalten ist nicht spezifiziert oder die Qualität der Implementierung ".
Filipp
@Filipp: Die Antwort ist eine Kombination - sie können überall erscheinen und unabhängig davon kann eine Implementierung nach der Zerstörung dieses Teils des Objekts (und vor der Zerstörung von Basisklassen) Speicher für einen Teil eines Objekts freigeben. Es ist einfach nicht erforderlich, dass Speicher erst nach der Zerstörung des gesamten Objekts freigegeben werden kann, unabhängig davon.
Chris Dodd
-1

Wenn Sie ein Objekt c vom Typ C mit den Basen A, B und einem Referenzzähler durch Erben von der Basis erstellen enable_shared_from_this<T>, wird zunächst Speicher für das gesamte resultierende Objekt zugewiesen, einschließlich der Basen im Allgemeinen und der Basis enable_shared_from_this<T>. Das Objekt wird erst zerstört, wenn der letzte Eigentümer (auch bekannt als shared_ptr) das Eigentum aufgibt. In diesem Moment werden ~ enable_shared ..., ~ B und ~ A nach ~ C ausgeführt. Der vollständig zugewiesene Speicher ist garantiert noch vorhanden, bis der letzte Destruktor ~ A ausgeführt wird. Nachdem ~ A ausgeführt wurde, wird der gesamte Objektspeicher auf einen Schlag freigegeben. Um Ihre Frage zu beantworten:

Kann ich beim Ausführen von ~ A () und ~ B () sicher sein, dass der Speicher, in dem C lebte, noch vorhanden ist?

Ja, obwohl Sie nicht legal darauf zugreifen können, aber warum sollten Sie es wissen müssen? Welches Problem versuchen Sie zu vermeiden?

Andreas_75
quelle
Was Sie geschrieben haben, ist wahr, beantwortet aber meine Frage nicht. Natürlich werden Basisklassendestruktoren nach der abgeleiteten Klasse ausgeführt. Ich frage, ob Implementierungen von shared_ptr, weak_ptrund enable_shared_from_thiserforderlich sind, um den Speicher lange genug zu halten, um dies sicher zu machen, auch wenn dies enable_shared_from_thisnicht die erste Basis ist.
Filipp
Ah ok. Betrachten Sie Ihre ursprüngliche Frage: Es gibt kein klares "dies" [wie unter "Speichern speichern" in Ihrem obigen Kommentar], das Sie erreichen möchten. Ich werde meine Antwort bearbeiten, um die Frage so wiederzugeben, wie ich sie derzeit verstehe.
Andreas_75
Machen Sie dies sicher = erben von enable_shared_from_thisnach einer anderen Basisklasse.
Filipp