Was ist der Nutzen von `enable_shared_from_this`?

349

Ich bin enable_shared_from_thisbeim Lesen der Boost.Asio-Beispiele darauf gestoßen, und nachdem ich die Dokumentation gelesen habe, bin ich immer noch verloren, wie dies richtig verwendet werden sollte. Kann mir bitte jemand ein Beispiel geben und erklären, wann die Verwendung dieser Klasse sinnvoll ist.

Fido
quelle

Antworten:

362

Es ermöglicht Ihnen, eine gültige shared_ptrInstanz zu erhalten this, wenn alles, was Sie haben, ist this. Ohne sie hätten Sie keine Möglichkeit, einen Zugang shared_ptrzu erhalten this, es sei denn, Sie hätten bereits einen als Mitglied. Dieses Beispiel aus der Boost-Dokumentation für 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 zurück shared_ptr, obwohl sie keine Mitgliedsinstanz hatte. Beachten Sie, dass Sie dies nicht einfach tun können:

class Y: public enable_shared_from_this<Y>
{
public:

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

Der gemeinsam genutzte Zeiger, den dieser zurückgegeben hat, hat eine andere Referenzanzahl als der "richtige", und einer von ihnen verliert und hält eine baumelnde Referenz, wenn das Objekt gelöscht wird.

enable_shared_from_thisist Teil des C ++ 11-Standards geworden. Sie können es auch von dort sowie von Boost bekommen.

1800 INFORMATIONEN
quelle
202
+1. Der entscheidende Punkt ist, dass die "offensichtliche" Technik, nur shared_ptr <Y> (this) zurückzugeben, nicht funktioniert, da dadurch mehrere unterschiedliche shared_ptr-Objekte mit separaten Referenzzählern erstellt werden. Aus diesem Grund dürfen Sie niemals mehr als einen shared_ptr aus demselben Rohzeiger erstellen .
j_random_hacker
3
Es soll beachtet werden , dass in C ++ 11 und später , ist es durchaus möglich , einen verwenden std::shared_ptrKonstruktor auf einem Rohzeiger wenn es erbt std::enable_shared_from_this. Ich weiß nicht, ob die Semantik von Boost aktualisiert wurde, um dies zu unterstützen.
Matthew
6
@MatthewHolder Hast du ein Angebot dafür? Auf cppreference.com habe ich gelesen: "Das Konstruieren eines std::shared_ptrfür ein Objekt, das bereits von einem anderen verwaltet std::shared_ptrwird, konsultiert nicht die intern gespeicherte schwache Referenz und führt daher zu undefiniertem Verhalten." ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
Thorbjørn Lindeijer
5
Warum kannst du es nicht einfach tun shared_ptr<Y> q = p?
Dan M.
2
@ ThorbjørnLindeijer, du hast recht, es ist C ++ 17 und höher. Einige Implementierungen folgten der C ++ 16-Semantik, bevor sie veröffentlicht wurden. Die richtige Handhabung für C ++ 11 bis C ++ 14 sollte verwendet werden std::make_shared<T>.
Matthew
198

Aus dem Artikel von Dr. Dobbs über schwache Zeiger geht hervor, dass dieses Beispiel leichter zu verstehen ist (Quelle: http://drdobbs.com/cpp/184402026 ):

... Code wie dieser funktioniert nicht richtig:

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

Keines der beiden shared_ptrObjekte kennt das andere, daher versuchen beide, die Ressource freizugeben, wenn sie zerstört werden. Das führt normalerweise zu Problemen.

Wenn eine Mitgliedsfunktion ein shared_ptrObjekt benötigt, das das Objekt besitzt, für das sie aufgerufen wird, kann sie nicht einfach ein Objekt im laufenden Betrieb 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 vorherige Beispiel, allerdings in einer subtileren Form. Wenn es erstellt wird, besitzt das shared_ptr-Objekt sp1die neu zugewiesene Ressource. Der Code in der Member-Funktion S::dangerouskennt dieses shared_ptrObjekt nicht, daher unterscheidet sich das zurückgegebene shared_ptrObjekt von sp1. Das Kopieren des neuen shared_ptrObjekts nach sp2hilft nicht. Wenn sp2der Gültigkeitsbereich verlassen wird, wird die Ressource freigegeben, und wenn sp1der Gültigkeitsbereich verlassen wird, wird die Ressource erneut freigegeben.

Um dieses Problem zu vermeiden, verwenden Sie die Klassenvorlage enable_shared_from_this. Die Vorlage verwendet ein Vorlagentypargument, bei dem es sich um den Namen der Klasse handelt, die die verwaltete Ressource definiert. Diese Klasse muss wiederum öffentlich aus der Vorlage abgeleitet werden. so was:

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;
}

Beachten Sie dabei, dass das Objekt, das Sie aufrufen shared_from_this, einem shared_ptrObjekt gehören muss. Das wird nicht funktionieren:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}
Artashes Aghajanyan
quelle
15
Vielen Dank, dies zeigt, dass das Problem besser gelöst wird als die derzeit akzeptierte Antwort.
Goertzenator
2
+1: Gute Antwort. Nebenbei bemerkt , anstatt shared_ptr<S> sp1(new S);es bevorzugt zu verwenden shared_ptr<S> sp1 = make_shared<S>();, siehe zum Beispiel stackoverflow.com/questions/18301511/…
Arun
4
Ich bin mir ziemlich sicher, dass die letzte Zeile lauten sollte, shared_ptr<S> sp2 = p->not_dangerous();da die Gefahr hier besteht, dass Sie einen shared_ptr auf normale Weise erstellen müssen, bevor Sie shared_from_this()das erste Mal aufrufen ! Das ist wirklich leicht falsch zu machen! Vor C ++ 17 muss UB aufrufen, shared_from_this()bevor genau ein shared_ptr auf normale Weise erstellt wurde: auto sptr = std::make_shared<S>();oder shared_ptr<S> sptr(new S());. Zum Glück wird dies ab C ++ 17 geworfen.
AnorZaken
2
SCHLECHTES Beispiel: S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();<- Es ist zulässig, shared_from_this nur für ein zuvor freigegebenes Objekt aufzurufen, dh für ein Objekt, das von std :: shared_ptr <T> verwaltet wird. Andernfalls ist das Verhalten undefiniert (bis C ++ 17). Std :: bad_weak_ptr wird ausgelöst (vom Shared_ptr-Konstruktor aus einem standardmäßig konstruierten schwachen_Das) (seit C ++ 17). . Die Realität ist also, dass es aufgerufen werden sollte always_dangerous(), weil Sie wissen müssen, ob es bereits geteilt wurde oder nicht.
AnorZaken
2
@AnorZaken Guter Punkt. Es wäre nützlich gewesen, wenn Sie eine Bearbeitungsanforderung gesendet hätten, um dieses Problem zu beheben. Ich habe es gerade getan. Die andere nützliche Sache wäre gewesen, wenn das Poster keine subjektiven, kontextsensitiven Methodennamen gewählt hätte!
underscore_d
30

Hier ist meine Erklärung aus der Perspektive der Schrauben und Muttern (die oberste Antwort hat bei mir nicht 'geklickt'). * Beachten Sie, dass dies das Ergebnis der Untersuchung der Quelle für shared_ptr und enable_shared_from_this ist, die mit Visual Studio 2012 geliefert wird. Möglicherweise implementieren andere Compiler enable_shared_from_this anders ... *

enable_shared_from_this<T>fügt eine private weak_ptr<T>Instanz hinzu, Tdie den ' one true reference count ' für die Instanz von enthält T.

Wenn Sie also zum ersten Mal ein shared_ptr<T>auf einem neuen T * erstellen , wird das interne schwache_ptr dieses T * mit einem Refcount von 1 initialisiert. Das Neue setzt im shared_ptrGrunde darauf zurück weak_ptr.

Tkann dann in seinen Methoden aufrufen shared_from_this, um eine Instanz davon zu erhalten shared_ptr<T>, die auf denselben intern gespeicherten Referenzzähler zurückgreift . Auf diese Weise haben Sie immer einen Ort, an dem T*die Ref-Zählung gespeichert wird, anstatt mehrere shared_ptrInstanzen zu haben, die nichts voneinander wissen, und jeder denkt, dass sie shared_ptrdiejenige sind, die für das Ref-Zählen Tund Löschen verantwortlich ist, wenn ihre Ref -count erreicht Null.

Mackenir
quelle
1
Dies ist richtig, und der wirklich wichtige Teil ist, So, when you first create...dass dies eine Anforderung ist (wie Sie sagen, wird der schwache_PTR erst initialisiert, wenn Sie den Objektzeiger an einen gemeinsam genutzten_PTR-Ctor übergeben!) Und in dieser Anforderung können Dinge schrecklich schief gehen, wenn Sie es sind nicht vorsichtig. Wenn Sie vor dem Aufruf kein shared_ptr erstellen, erhalten shared_from_thisSie UB. Wenn Sie mehr als ein shared_ptr erstellen, erhalten Sie auch UB. Sie müssen irgendwie sicherstellen, dass Sie einen shared_ptr genau einmal erstellen .
AnorZaken
2
Mit anderen Worten, die ganze Idee von enable_shared_from_thisist anfangs spröde, da es darum geht, a shared_ptr<T>von a zu erhalten T*, aber in Wirklichkeit T* tist es im Allgemeinen nicht sicher, wenn Sie einen Zeiger erhalten, davon auszugehen, dass er bereits geteilt wurde oder nicht, und Die falsche Vermutung ist UB.
AnorZaken
" internes schwaches_ptr wird mit einer Refcount von 1 initialisiert " schwaches ptr zu T besitzen kein intelligentes ptr zu T. Ein schwaches ptr ist ein besitzender intelligenter Ref zu genügend Informationen, um ein besitzendes ptr zu erstellen, das eine "Kopie" eines anderen besitzenden ptr ist. Ein schwacher ptr hat keine Referenzanzahl. Es hat Zugriff auf eine Ref-Zählung, wie alle, die Ref besitzen.
Neugieriger
3

Beachten Sie, dass die Verwendung von boost :: intrusive_ptr nicht unter diesem Problem leidet. Dies ist oft eine bequemere Möglichkeit, um dieses Problem zu umgehen.

blais
quelle
Ja, aber enable_shared_from_thisSie können mit einer API arbeiten, die dies ausdrücklich akzeptiert shared_ptr<>. Meiner Meinung nach macht eine solche API normalerweise etwas falsch (da es besser ist, etwas Höheres im Stapel den Speicher besitzen zu lassen), aber wenn Sie gezwungen sind, mit einer solchen API zu arbeiten, ist dies eine gute Option.
cdunn2001
2
Es ist besser, so weit wie möglich innerhalb des Standards zu bleiben.
Sergei
3

In c ++ 11 und höher ist es genau dasselbe: Es soll die Möglichkeit ermöglichen, thisals gemeinsam genutzter Zeiger zurückzukehren, da thisSie einen Rohzeiger erhalten.

Mit anderen Worten, Sie können Code wie folgt drehen

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

private:

    Node * m_parent = nullptr;
};           

das sehr gut finden:

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;
};           
mchiasson
quelle
Dies funktioniert nur, wenn diese Objekte immer von a verwaltet werden shared_ptr. Möglicherweise möchten Sie die Benutzeroberfläche ändern, um sicherzustellen, dass dies der Fall ist.
Neugieriger
1
Sie sind absolut korrekt @curiousguy. Das ist selbstverständlich. Ich mag es auch, alle meine shared_ptr zu tippen, um die Lesbarkeit beim Definieren meiner öffentlichen APIs zu verbessern. Zum Beispiel std::shared_ptr<Node> getParent const()würde ich es normalerweise NodePtr getParent const()stattdessen als stattdessen aussetzen . Wenn Sie unbedingt Zugriff auf den internen Raw-Zeiger benötigen (bestes Beispiel: Umgang mit einer C-Bibliothek), gibt es std::shared_ptr<T>::getdafür etwas, das ich nicht gerne erwähne, weil ich diesen Raw-Zeiger-Accessor aus dem falschen Grund zu oft verwendet habe.
Mchiasson
-3

Eine andere Möglichkeit besteht darin, ein weak_ptr<Y> m_stubMitglied zum hinzuzufügen class Y. Dann schreibe:

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

Nützlich, wenn Sie die Klasse, aus der Sie stammen, nicht ändern können (z. B. die Bibliothek anderer Personen erweitern). Vergessen Sie nicht, das Mitglied zu initialisieren, z. B. durchm_stub = shared_ptr<Y>(this) , es ist auch während eines Konstruktors gültig.

Es ist in Ordnung, wenn mehr Stubs wie dieser in der Vererbungshierarchie vorhanden sind. Dies verhindert nicht die Zerstörung des Objekts.

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

PetrH
quelle
4
Wenn Sie hier beabsichtigen, einen zu produzieren, shared_ptr<>der seinen Pointee nicht löscht, ist dies ein Overkill. Sie können einfach sagen, return shared_ptr<Y>(this, no_op_deleter);wo no_op_deletersich ein unäres Funktionsobjekt befindet Y*, das nichts nimmt und tut.
John Zwinck
2
Es ist unwahrscheinlich, dass dies eine funktionierende Lösung ist. m_stub = shared_ptr<Y>(this)erstellt und zerstört sofort einen temporären shared_ptr daraus. Wenn diese Anweisung beendet ist, thiswird sie gelöscht und alle nachfolgenden Verweise baumeln.
Nobar
2
Der Autor erkennt an, dass diese Antwort falsch ist, sodass er sie wahrscheinlich einfach löschen könnte. Aber er hat sich zuletzt in 4,5 Jahren angemeldet und wird es wahrscheinlich nicht tun - könnte jemand mit höheren Kräften diesen roten Hering entfernen?
Tom Goodfellow
Wenn Sie sich die Implementierung von ansehen enable_shared_from_this, behält sie ein weak_ptrvon sich selbst (vom ctor ausgefüllt) bei, das shared_ptrbeim Aufruf zurückgegeben wird shared_from_this. Mit anderen Worten, Sie duplizieren das, was enable_shared_from_thisbereits vorhanden ist.
Mchiasson