2010-10-10 7 views
73

Mr. Lidström and I had an argument :)Shared_ptr Magie :)

Herr Lidström Behauptung ist, dass ein Konstrukt shared_ptr<Base> p(new Derived); keine Basis erfordern einen virtuellen Destruktor zu haben:

Armen Tsirunyan: „Wirklich Wird die shared_ptr sauber aufräumen? Können Sie bitte in diesem Fall demonstrieren, wie dieser Effekt implementiert werden könnte? "

Daniel Lidström: „Der Shared_ptr verwendet seinen eigenen destructor den Beton Instanz löscht Dies als RAII in der C-Community ++ bekannt Mein Rat ist, dass Sie alle lernen Sie RAII können Sie machen Ihre... C++ - Codierung ist so viel einfacher, wenn Sie RAII in allen Situationen verwenden. "

Armen Tsirunyan: „Ich weiß über RAII, und ich weiß auch, dass schließlich die Shared_ptr destructor die gespeicherte px löschen kann, wenn pn 0 erreicht Aber wenn px hatte statischen Typ Zeiger auf Base und dynamischen Typ Zeiger auf Wenn Base einen virtuellen Destruktor hat, wird dies zu einem undefinierten Verhalten führen.

Daniel Lidström: „Die Shared_ptr weiß der statische Typ Beton ist es weiß das, weil ich es in seinem Konstruktor übergeben scheint ein bisschen wie Magie, aber ich kann Ihnen versichern, dass es durch Design und extrem nett.! . "

Also, urteilen Sie uns. Wie ist es möglich (wenn ja), shared_ptr zu implementieren, ohne dass polymorphe Klassen virtuellen Destruktor haben? Vielen Dank im Voraus

+3

Sie könnten mit dem [ursprünglichen Thread] (http://stackoverflow.com/questions/3899688/default-virtual-dtor/3899726) verknüpft haben. –

+0

@Darin: Ich habe es getan. – sbi

+7

Eine andere interessante Sache ist, dass 'shared_ptr p (new Derived)' auch das 'Derived' Objekt durch seinen Destruktor zerstören wird, unabhängig davon, ob es' virtual' ist oder nicht. – dalle

Antwort

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. Um Ihr Beispiel funktioniert, brauchen wir einen Templat-Konstruktor:

template<class T> 
class shared_ptr 
{ 
public: 
    ... 
    template<class Y> 
    explicit shared_ptr(Y* p); 
    ... 
}; 

Also, wenn Sie diese verwenden, um mit Ihren Klassen Basis und Abgeleitet ...

class Base {}; 
class Derived : public Base {}; 

int main() { 
    shared_ptr<Base> sp (new Derived); 
} 

... das Templat-Konstruktor mit Y = Abgeleitet wird verwendet, um das shared_ptr-Objekt zu erstellen. 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 auf Ableitungen bezogene Deleter verwendet, um das Objekt zu beseitigen.

Der C++ 11-Standard hat die folgenden zu diesem Konstruktor zu sagen (20.7.2.2.1):

Benötigt:p muss T* konvertierbar sein. Y soll 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 dass besitzt den Zeiger p.

...

Und für die destructor (20.7.2.2.2):

Effekte: Wenn *this ist leer oder Aktien Eigentum mit anderen shared_ptr Instanz (use_count() > 1) , es gibt keine Nebenwirkungen. Andernfalls, wenn *this besitzt ein Objekt und ein Deleter d, wird d(p) aufgerufen. Andernfalls, wenn *this besitzt einen Zeiger , und delete p heißt.

(Hervorhebung mit Fettschrift ist meins).

+0

+1 für das Code-Snippet, um den Typ auszulösen, der passiert. – legends2k

+0

'Der kommende Standard erfordert auch dieses Verhalten ': (a) Welchen Standard und (b) können Sie bitte einen Verweis (auf den Standard) geben? – kevinarpe

13

einfach,

shared_ptr spezielle deleter Funktion verwendet, die von Konstruktor erstellt wird, den destructor des gegebenen Objekts und nicht die destructor of Base verwendet immer, das ist ein bisschen Arbeit mit Vorlage Meta Programmierung, aber es funktioniert.

etwas wie das

template<typename SomeType> 
shared_ptr(SomeType *p) 
{ 
    this->destroyer = destroyer_function<SomeType>(p); 
    ... 
} 
+0

hmm ... interessant, ich fange an, das zu glauben :) –

+1

@Armen Tsirunyan Du hättest in die Design-Beschreibung von shared_ptr gucken müssen, bevor du das discussionon startest. Diese "Erfassung des Löschers" ist eines der wesentlichen Merkmale von shared_ptr ... –

+5

@ paul_71: Ich stimme Ihnen zu. Auf der anderen Seite glaube ich, dass diese Diskussion nicht nur für mich nützlich war, sondern auch für andere Leute, die diese Tatsache über shared_ptr nicht wussten. Also ich denke, es war keine große Sünde, diesen Thread trotzdem zu starten :) –

26

Wenn Shared_ptr es erstellt wird speichert eine 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.

Zum Beispiel können Sie eine benutzerdefinierte deleter erstellen:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. 

shared_ptr<Base> p(new Derived, DeleteDerived); 

p wird DeleteDerived nennen sich die spitzen Gegenstand zu zerstören. Die Implementierung macht dies automatisch.

+4

+1 für die Bemerkung über unvollständige Typen, sehr praktisch, wenn ein 'shared_ptr' als Attribut verwendet wird. –