2009-04-03 5 views
270

Ich lief über enable_shared_from_this beim Lesen der Boost.Asio Beispiele und nach dem Lesen der Dokumentation bin ich immer noch verloren, wie dies richtig verwendet werden sollte. Kann mir bitte jemand ein Beispiel geben und/oder eine Erklärung für die Verwendung dieser Klasse sinnvoll ist.Was ist der Nutzen von `enable_shared_from_this`?

Antwort

285

Es ermöglicht Ihnen, eine gültige shared_ptr Instanz zu this zu erhalten, wenn alles, was Sie haben, this ist. Ohne sie hätten Sie keine Möglichkeit, einen zu this zu bekommen, es sei denn, Sie hatten bereits einen als Mitglied. Dieses Beispiel aus dem boost documentation for enable_shared_from_this:

class Y: public enable_shared_from_this<Y> 
{ 
public: 

    shared_ptr<Y> f() 
    { 
     return shared_from_this(); 
    } 
} 

int main() 
{ 
    shared_ptr<Y> p(new Y); 
    shared_ptr<Y> q = p->f(); 
    assert(p == q); 
    assert(!(p < q || q < p)); // p and q must share ownership 
} 

Die Methode f() gibt eine gültige shared_ptr, obwohl es kein Mitglied Instanz hatte. Beachten Sie, dass dies nicht einfach tun:

class Y: public enable_shared_from_this<Y> 
{ 
public: 

    shared_ptr<Y> f() 
    { 
     return shared_ptr<Y>(this); 
    } 
} 

Der gemeinsame Zeiger, der diese ein anderen Referenzzähler haben aus dem „richtigen“ einem zurückgegeben wird, und einer von ihnen wird am Ende verlieren und hielt eine baumelnde Referenz, wenn das Objekt ist gelöscht.

enable_shared_from_this wird auch ein Teil des neuen C++ 0x-Standards sein, so dass Sie es auch von dort sowie von boost bekommen können.

+155

+1. Der springende Punkt ist, dass die "offensichtliche" Technik der Rückgabe von shared_ptr (this) gebrochen ist, weil dies mehrere separate shared_ptr-Objekte mit separaten Referenzzählungen erzeugt. Aus diesem Grund dürfen Sie niemals mehr als eine shared_ptr ** vom selben rohen Zeiger ** erstellen. –

+0

Es sollte angemerkt werden, dass es in _C++ 11 und später_ absolut zulässig ist, einen 'std :: shared_ptr'-Konstruktor für einen _raw-Zeiger_ ** zu verwenden, wenn ** er von' std :: enable_shared_from_this' erbt . ** Ich weiß nicht, ob ** Boosts Semantik aktualisiert wurde, um dies zu unterstützen. –

-2

Eine andere Möglichkeit besteht darin, ein weak_ptr<Y> m_stub Element in hinzuzufügen. Dann schreiben:

shared_ptr<Y> Y::f() 
{ 
    return m_stub.lock(); 
} 

Nützlich, wenn Sie nicht die Klasse ändern können, von dem Sie ableiten (z anderer Leute Bibliothek erstreckt). Vergessen Sie nicht, das Mitglied zu initialisieren, z. von m_stub = shared_ptr<Y>(this), ist es auch während eines Konstruktors gültig.

Es ist in Ordnung, wenn es mehr Stubs wie dieses in der Vererbungshierarchie gibt, es wird die Zerstörung des Objekts nicht verhindern.

Bearbeiten: Wie von Benutzer nobar richtig angezeigt, würde der Code Y-Objekt zerstören, wenn die Zuweisung abgeschlossen ist und temporäre Variablen zerstört werden. Daher ist meine Antwort falsch.

+3

Wenn Sie hier einen 'shared_ptr <>' erstellen möchten, der seinen Pointe nicht löscht, ist das Overkill.Sie können einfach 'return shared_ptr (this, no_op_deleter);' sagen, wobei 'no_op_deleter' ein unäres Funktionsobjekt ist, das' Y * 'übernimmt und nichts tut. –

+1

Es scheint unwahrscheinlich, dass dies eine funktionierende Lösung ist. 'm_stub = shared_ptr (this)' wird daraus ein temporäres shared_ptr konstruieren und sofort zerstören. Wenn diese Aussage zu Ende ist, wird 'this 'gelöscht und alle nachfolgenden Referenzen werden baumeln. – nobar

+1

Der Autor bestätigt, dass diese Antwort falsch ist, also könnte er sie wahrscheinlich löschen. Aber er hat sich zuletzt 4,5 Jahre eingeloggt und wird es wahrscheinlich nicht tun - könnte jemand mit höheren Kräften diesen roten Hering entfernen? –

153

von Dr. Dobbs Artikel über schwache Zeiger, ich denke, das Beispiel leichter zu verstehen ist (Quelle: http://drdobbs.com/cpp/184402026):

... Code wie diesen wird nicht korrekt funktionieren:

int *ip = new int; 
shared_ptr<int> sp1(ip); 
shared_ptr<int> sp2(ip); 

Weder von den zwei shared_ptr Objekten weiß über die anderen, so werden beide versuchen, die Ressource freizugeben, wenn sie zerstört werden. Das führt normalerweise zu Problemen.

Und falls eine Elementfunktion ein shared_ptr Objekt benötigt, das das Objekt besitzt, dass es auf genannt wird, kann es nicht nur ein Objekt on the fly erstellen:

struct S 
{ 
    shared_ptr<S> dangerous() 
    { 
    return shared_ptr<S>(this); // don't do this! 
    } 
}; 

int main() 
{ 
    shared_ptr<S> sp1(new S); 
    shared_ptr<S> sp2 = sp1->dangerous(); 
    return 0; 
} 

Dieser Code hat das gleiche Problem wie das frühere Beispiel, obwohl in einer subtileren Form. Wenn es aufgebaut ist, besitzt das Objekt shared_pt r sp1 die neu zugeordnete Ressource. Der Code in der Elementfunktion S::dangerous kennt das shared_ptr Objekt nicht, so dass das shared_ptr Objekt, das zurückgegeben wird, sich von sp1 unterscheidet.Das Kopieren des neuen Objekts shared_ptr zu sp2 hilft nicht; Wenn sp2 den Gültigkeitsbereich verlässt, wird die Ressource freigegeben, und wenn sp1 den Gültigkeitsbereich verlässt, wird die Ressource erneut freigegeben.

Um dieses Problem zu vermeiden, verwenden Sie die Klassenvorlage enable_shared_from_this. Die Vorlage verwendet ein Template-Argument, das den Namen der Klasse angibt, die die verwaltete Ressource definiert. Diese Klasse muss wiederum öffentlich aus der Vorlage abgeleitet werden; dies wie:

struct S : enable_shared_from_this<S> 
{ 
    shared_ptr<S> not_dangerous() 
    { 
    return shared_from_this(); 
    } 
}; 

int main() 
{ 
    shared_ptr<S> sp1(new S); 
    shared_ptr<S> sp2 = sp1->not_dangerous(); 
    return 0; 
} 

Wenn Sie dies tun, denken Sie daran, dass das Objekt, auf dem Sie shared_from_this nennen muss von einem shared_ptr Objekt gehören. Das wird nicht funktionieren:

int main() 
{ 
    S *p = new S; 
    shared_ptr<S> sp2 = p->not_dangerous();  // don't do this 
} 
+7

Danke, das veranschaulicht, dass das Problem besser gelöst wird als die derzeit akzeptierte Antwort. – goertzenator

+1

+1: Gute Antwort. Nebenbei bemerkt, anstelle von 'shared_ptr sp1 (neues S);' kann es bevorzugt sein, 'shared_ptr sp1 = make_shared ();' zu verwenden, siehe zum Beispiel http://stackoverflow.com/questions/18301511/stdshared -ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo – Arun

+3

Ich bin mir ziemlich sicher, dass die letzte Zeile lesen sollte: shared_ptr sp2 = p-> not_dangerous(); 'weil der Fehler hier ist, dass Sie ** müssen Erstellen Sie einen shared_ptr auf die normale Art und Weise, bevor Sie 'shared_from_this()' das erste Mal aufrufen! ** Das ist wirklich leicht falsch! Vor C++ 17 ist es ** UB **, 'shared_from_this()' aufzurufen, bevor genau ein shared_ptr normal erstellt wurde: 'auto sptr = std :: make_shared ();' oder 'shared_ptr sptr (neu S()); '. Glücklicherweise ab C++ 17 wird dies tun. – AnorZaken

3

Beachten Sie, dass eine boost :: mit intrusive_ptr von diesem Problem nicht betroffen. Dies ist oft eine bequemere Möglichkeit, dieses Problem zu umgehen.

+0

Ja, aber 'enable_shared_from_this' ermöglicht es Ihnen, mit einer API zu arbeiten, die' shared_ptr <> 'ausdrücklich akzeptiert. Meiner Meinung nach ist eine solche API in der Regel * Doing It Wrong * (da es besser ist, etwas höher im Stack den Speicher besitzen zu lassen), aber wenn man gezwungen ist, mit einer solchen API zu arbeiten, ist dies eine gute Option. – cdunn2001

23

Hier ist meine Erklärung, aus der Perspektive der Schrauben und Muttern (Top-Antwort nicht mit mir "klick"). * Anmerkung, dass dies das Ergebnis einer Untersuchung der Quelle für shared_ptr und enable_shared_from_this, die mit Visual Studio kommt 2012. Vielleicht anderen Compilern enable_shared_from_this anders implementieren ... *

enable_shared_from_this<T> fügt eine private weak_ptr<T> Instanz T, die die "hält man wahre Referenzzahl 'für die Instanz T.

Also, wenn Sie zuerst ein shared_ptr<T> auf einen neuen T * schaffen, dass T * 's internen weak_ptr mit einem refcount von 1. Die neuen shared_ptr grundsätzlich auf diesen weak_ptr sichert initialisiert wird.

T kann dann in seine Methoden, shared_from_this rufen eine Instanz von shared_ptr<T> dass Rücken auf derselben intern gespeicherten Referenzzähler zu erhalten. Auf diese Weise haben Sie immer einen Platz, an dem T* 's ref-count gespeichert wird, anstatt mehrere shared_ptr Instanzen, die nicht voneinander wissen, und jeder denkt, dass sie die shared_ptr sind, die für ref-counting T zuständig ist und löschen es wenn ihre ref-count Null erreicht.

+1

Das ist richtig, und der wirklich wichtige Teil ist 'So, wenn du zuerst erstellst ...' weil das eine ** Voraussetzung ** ist (wie du sagst, wird der weak_ptr nicht initialisiert, bis du den Objektzeiger in einen shared_ptr übergibst ctor!) und diese Voraussetzung ist, wo Dinge schrecklich schief gehen können, wenn Sie nicht vorsichtig sind. Wenn Sie vor dem Aufruf von 'shared_from_this' kein shared_ptr erstellen, erhalten Sie UB - ebenfalls, wenn Sie mehr als ein shared_ptr erstellen, erhalten Sie auch UB. Sie müssen irgendwie sicherstellen, dass Sie einmal ein shared_ptr _exactly_ erstellen. – AnorZaken

+1

Mit anderen Worten, die ganze Idee von 'enable_shared_from_this' ist spröde, da der Punkt in der Lage ist, ein' shared_ptr 'von einem' T * 'zu erhalten, aber in Wirklichkeit, wenn Sie einen Zeiger' T * t' bekommen Es ist im Allgemeinen nicht sicher anzunehmen, dass etwas bereits geteilt wird oder nicht, und die falsche Schätzung ist UB. – AnorZaken

+0

"_internal weak_ptr wird mit einem refcount von 1_ initialisiert" schwache ptr zu T sind nicht besitzende intelligente ptr zu T. Ein schwacher ptr ist ein intelligenter Verweis auf genügend Informationen, um ein besitzendes ptr zu erstellen, das eine "Kopie" anderer besitzender ptr ist . Ein schwaches PTR hat keine Referenzzählung. Es hat Zugriff auf eine Ref-Zahl, wie alle ref. – curiousguy

2

Es ist genau das gleiche in C++ 11 und höher: Es ist die Fähigkeit zu ermöglichen, this als gemeinsamer Zeiger zurückgeben, da this gibt Ihnen einen rohen Zeiger.

in anderen Worten, es können Sie Code wie dieser

class Node { 
public: 
    Node* getParent const() { 
     if (m_parent) { 
      return m_parent; 
     } else { 
      return this; 
     } 
    } 

private: 

    Node * m_parent = nullptr; 
};   

in diese einzuschalten:

class Node : std::enable_shared_from_this<Node> { 
public: 
    std::shared_ptr<Node> getParent const() { 
     std::shared_ptr<Node> parent = m_parent.lock(); 
     if (parent) { 
      return parent; 
     } else { 
      return shared_from_this(); 
     } 
    } 

private: 

    std::weak_ptr<Node> m_parent; 
};   
+0

Dies funktioniert nur, wenn diese Objekte immer von 'shared_ptr' verwaltet werden. Möglicherweise möchten Sie die Schnittstelle ändern, um sicherzustellen, dass dies der Fall ist. – curiousguy

+0

Sie sind absolut richtig @curiousguy. Dies ist selbstverständlich.Ich mag auch typedef alle meine shared_ptr, um die Lesbarkeit bei der Definition meiner öffentlichen APIs zu verbessern. Zum Beispiel, anstelle von 'std :: shared_ptr getParent const()', würde ich es normalerweise als 'NodePtr getParent const()' stattdessen verfügbar machen. Wenn Sie unbedingt Zugriff auf den internen RAW-Pointer benötigen (bestes Beispiel: Umgang mit einer C-Bibliothek), gibt es 'std :: shared_ptr :: get 'dafür, was ich nicht gerne erwähne, weil ich diesen rohen Pointer-Accessor zu oft benutzt habe mal aus dem falschen Grund. – mchiasson

Verwandte Themen