shared_ptr magic :)

73

Mr. Lidström und ich hatten einen Streit :)

Herr. Lidströms Behauptung ist, dass ein Konstrukt shared_ptr<Base> p(new Derived); nicht benötigt, dass Base einen virtuellen Destruktor hat:

  

Armen Tsirunyan : Wirklich? Wird shared_ptr richtig aufgeräumt? Könnten Sie bitte in diesem Fall demonstrieren, wie dieser Effekt implementiert werden könnte?

     

Daniel Lidström : Der shared_ptr verwendet einen eigenen Destruktor, um die Concrete-Instanz zu löschen. Dies wird in der C ++ - Community RAII genannt. Mein Rat ist, dass Sie alles lernen Sie können über RAII. Es wird Ihre C ++ - Codierung so viel einfacher machen, wenn Sie RAII in allen Situationen verwenden. "

     

Armen Tsirunyan : Ich kenne RAII, und ich weiß auch, dass der shared_ptr Destruktor möglicherweise den gespeicherten px löscht, wenn pn 0 erreicht. Aber wenn px statisch ist Geben Sie den Zeiger auf Base und den Zeiger des dynamischen Typs auf Derived ein, wenn nicht Base einen virtuellen Destruktor hat, führt dies zu undefiniertem Verhalten. Korrigieren Sie mich, wenn ich falsch liege. "

     

Daniel Lidström : "Der shared_ptr kennt den statischen Typ" Concrete ". Er weiß es, seit ich es in seinem Konstruktor übergeben habe! Scheint ein bisschen wie Magie, aber ich kann versichere Ihnen, dass es von Design und extrem nett ist. "

Also, urteile über uns. Wie ist es möglich (wenn), shared_ptr zu implementieren, ohne dass polymorphe Klassen einen virtuellen Destruktor haben müssen? Vielen Dank im Voraus

    
Armen Tsirunyan 10.10.2010, 09:41
quelle

3 Antworten

66

Ja, es ist möglich, shared_ptr auf diese Weise zu implementieren. Boost tut das und der C ++ 11-Standard erfordert auch dieses Verhalten. Als zusätzliche Flexibilität verwaltet shared_ptr mehr als nur einen Referenzzähler. Ein sogenannter Löscher wird normalerweise in denselben Speicherblock gesetzt, der auch die Referenzzähler enthält. Der spaßige Teil ist jedoch, dass der Typ dieses Deleters nicht Teil des Typs shared_ptr ist. Dies wird als "Typ löschen" bezeichnet und ist im Grunde die gleiche Technik, die zum Implementieren der "polymorphen Funktionen" boost :: function oder std :: function zum Verbergen des tatsächlichen Funktortyps verwendet wird. Damit Ihr Beispiel funktionieren kann, benötigen wir einen Vorlagenkonstruktor:

%Vor%

Also, wenn Sie dies mit Ihren Klassen Base und Derived ... verwenden

%Vor%

... wird der Konstruktor mit dem Template mit Y = Derived verwendet, um das Objekt shared_ptr zu konstruieren. Der Konstruktor hat somit die Möglichkeit, die entsprechenden Löschobjekt- und Referenzzähler zu erstellen und einen Zeiger auf diesen Steuerblock als Datenelement zu speichern. Wenn der Referenzzähler null erreicht, wird der zuvor erstellte und abgeleitete Deleter verwendet, um das Objekt zu entfernen.

Der C ++ 11-Standard besagt Folgendes über diesen Konstruktor (20.7.2.2.1):

  

Voraussetzung: p muss in T* konvertierbar sein. Y muss ein vollständiger Typ sein. Der Ausdruck delete p soll wohlgeformt sein, wohldefiniertes Verhalten haben und keine Ausnahmen auslösen.

     

Effekte: Konstruiert ein shared_ptr Objekt das besitzt den Zeiger p .

     

...

Und für den Destruktor (20.7.2.2.2):

  

Effekte: Wenn *this leer ist oder das Eigentum an einer anderen shared_ptr -Instanz ( use_count() > 1 ) teilt, gibt es keine Nebenwirkungen.   Andernfalls, wenn *this ein Objekt p besitzt und ein Deleter d , wird d(p) aufgerufen.    Andernfalls, wenn *this einen Zeiger p besitzt und delete p aufgerufen wird.

(Hervorhebung mit Fettschrift gehört mir).

    
sellibitze 10.10.2010, 11:18
quelle
26

Wenn shared_ptr erstellt wird, speichert es ein Deleter Objekt in sich. Dieses Objekt wird aufgerufen, wenn das shared_ptr die angegebene Ressource freigibt. Da Sie wissen, wie Sie die Ressource zum Zeitpunkt der Konstruktion zerstören können, können Sie shared_ptr mit unvollständigen Typen verwenden. Wer auch immer shared_ptr erstellt hat, hat dort einen korrekten Deleter gespeichert.

Sie können beispielsweise einen benutzerdefinierten Löschvorgang erstellen:

%Vor%

p ruft DeleteDerived auf, um das spitze Objekt zu zerstören. Die Implementierung macht dies automatisch.

    
ybungalobill 10.10.2010 09:47
quelle
13

Einfach,

shared_ptr verwendet eine spezielle Löschfunktion, die vom Konstruktor erstellt wird, der immer verwendet der Destruktor des gegebenen Objekts und nicht der Destruktor von Base, das ist ein bisschen Arbeit mit Template-Meta-Programmierung, aber es funktioniert.

So ähnlich

%Vor%     
Artyom 10.10.2010 09:46
quelle

Tags und Links