Was ist die Nützlichkeit von `enable_shared_from_this`?

Lesezeit: 5 Minuten

Was ist die Nutzlichkeit von enable shared from this
fido

Ich rannte hinüber enable_shared_from_this Beim Lesen der Boost.Asio-Beispiele und nach dem Lesen der Dokumentation weiß ich immer noch nicht, wie dies richtig verwendet werden sollte. Kann mir bitte jemand ein Beispiel und eine Erklärung geben, wann die Verwendung dieser Klasse sinnvoll ist.

1646919013 17 Was ist die Nutzlichkeit von enable shared from this
1800 INFORMATIONEN

Es ermöglicht Ihnen, eine gültige zu erhalten shared_ptr Instanz zu thiswenn alles, was Sie haben, ist this. Ohne sie hätten Sie keine Möglichkeit, eine zu bekommen shared_ptr zu this, es sei denn, Sie hatten bereits eine 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 ein gültiges zurück shared_ptr, obwohl es 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, der dadurch zurückgegeben wird, hat eine andere Referenzanzahl als die “richtige”, und einer von ihnen wird am Ende verlieren und eine baumelnde Referenz halten, wenn das Objekt gelöscht wird.

enable_shared_from_this ist Teil des C++ 11-Standards geworden. Sie können es auch von dort sowie von Boost erhalten.

  • +1. Der entscheidende Punkt ist, dass die “offensichtliche” Technik, nur shared_ptr(this) zurückzugeben, kaputt ist, da dies dazu führt, dass mehrere unterschiedliche shared_ptr-Objekte mit separaten Referenzzählern erstellt werden. Aus diesem Grund dürfen Sie niemals mehr als einen shared_ptr erstellen aus demselben rohen Zeiger.

    – j_random_hacker

    3. April 2009 um 2:31 Uhr

  • Zu beachten ist, dass in C++11 und höheres ist vollkommen gültig ein verwenden std::shared_ptr Konstrukteur auf a roher Zeiger wenn es erbt von std::enable_shared_from_this. Ich weiß nicht, ob Die Semantik von Boost wurde aktualisiert, um dies zu unterstützen.

    – Matthew

    10. Oktober 2017 um 20:51 Uhr


  • @MatthewHolder Hast du ein Zitat dafür? Auf cppreference.com las ich „Constructing a std::shared_ptr für ein Objekt, das bereits von einem anderen verwaltet wird std::shared_ptr wird die intern gespeicherte schwache Referenz nicht konsultieren und führt somit zu undefiniertem Verhalten.” (en.cppreference.com/w/cpp/memory/enable_shared_from_this)

    – Thorbjørn Lindeijer

    1. Mai 2018 um 14:01 Uhr

  • Warum kannst du nicht einfach tun shared_ptr<Y> q = p?

    –Dan M.

    8. Mai 2018 um 14:28 Uhr

  • @DanM. Sie können, deshalb ist dieses Beispiel nicht sehr nützlich. Es gibt aber definitiv Anwendungsfälle dafür. Wenn es keine gibt q und du brauchst ein p aus der Klasse.

    – Hatted Hahn

    5. Januar 2019 um 14:23 Uhr

Was ist die Nutzlichkeit von enable shared from this
Artashes Aghajanyan

Aus dem Artikel von Dr. Dobbs über schwache Hinweise denke ich, dass dieses Beispiel leichter zu verstehen ist (Quelle: http://drdobs.com/cpp/184402026):

… Code wie dieser wird nicht richtig funktionieren:

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

Keines von beidem shared_ptr Objekte wissen voneinander, also werden beide versuchen, die Ressource freizugeben, wenn sie zerstört werden. Das führt meist zu Problemen.

Ebenso, wenn eine Mitgliedsfunktion a benötigt shared_ptr Objekt, das das aufgerufene Objekt besitzt, kann es 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, wenn auch in einer subtileren Form. Wenn es gebaut wird, die shared_ptr-Objekt sp1 besitzt die neu zugewiesene Ressource. Der Code innerhalb der Member-Funktion S::dangerous weiß davon nichts shared_ptr Objekt, also die shared_ptr Objekt, das es zurückgibt, unterscheidet sich von sp1. Kopieren des Neuen shared_ptr widersprechen sp2 hilft nicht; wann sp2 außerhalb des Geltungsbereichs liegt, wird die Ressource freigegeben und wann sp1 den Geltungsbereich verlässt, wird die Ressource wieder freigegeben.

Um dieses Problem zu vermeiden, verwenden Sie die Klassenvorlage enable_shared_from_this. Die Vorlage akzeptiert ein Vorlagentypargument, das der Name der Klasse ist, die die verwaltete Ressource definiert. Diese Klasse muss wiederum öffentlich von 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;
}

Denken Sie dabei daran, dass das Objekt, das Sie aufrufen shared_from_this muss im Besitz von a sein shared_ptr Objekt. Das wird nicht funktionieren:

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

  • Danke, dies zeigt, dass das Problem besser gelöst wird als die derzeit akzeptierte Antwort.

    – goertzenator

    2. Mai 2013 um 14:04 Uhr

  • +1: Gute Antwort. Nebenbei, statt shared_ptr<S> sp1(new S); es kann bevorzugt verwendet werden shared_ptr<S> sp1 = make_shared<S>();siehe zum Beispiel stackoverflow.com/questions/18301511/…

    – Arun

    1. April 2015 um 20:27 Uhr

  • Ich bin mir ziemlich sicher, dass die letzte Zeile lauten sollte shared_ptr<S> sp2 = p->not_dangerous(); denn die Falle hier ist, dass Sie müssen Sie vor dem Anruf auf die übliche Weise einen shared_ptr erstellen shared_from_this() das erste Mal! Das ist wirklich leicht falsch zu machen! Vor C++17 ist es UB anrufen 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());. Glücklicherweise wird dies ab C++17 werfen.

    – AnorZaken

    1. September 2016 um 15:48 Uhr


  • Schlechtes Beispiel: S* s = new S(); shared_ptr<S> ptr = s->not_dangerous(); <-- Der Aufruf von shared_from_this ist nur auf einem zuvor freigegebenen Objekt erlaubt, dh auf einem von std::shared_ptr verwalteten Objekt. Andernfalls ist das Verhalten undefiniert (bis C++17)std::bad_weak_ptr ausgelöst wird (durch den shared_ptr-Konstruktor von einem standardmäßig konstruierten schwachen_dies) (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

    1. September 2016 um 15:56 Uhr


  • @AnorZaken Guter Punkt. Es wäre hilfreich gewesen, wenn Sie eine Bearbeitungsanfrage gesendet hätten, um diese Korrektur vorzunehmen. Ich habe es gerade getan. Die andere nützliche Sache wäre gewesen, wenn das Poster keine subjektiven, kontextsensitiven Methodennamen gewählt hätte!

    – Unterstrich_d

    12. Dezember 2016 um 10:06 Uhr

Hier ist meine Erklärung aus der Sicht der Muttern und Schrauben (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 in Visual Studio 2012 enthalten ist. Vielleicht implementieren andere Compiler enable_shared_from_this anders…*

enable_shared_from_this<T> fügt ein privates hinzu weak_ptr<T> Instanz zu T das hält die ‘eine echte Referenz zählen‘ zum Beispiel T.

Wenn Sie also zum ersten Mal eine shared_ptr<T> auf ein neues T*, wird der interne schwache_ptr dieses T* mit einem Refcount von 1 initialisiert. Der neue shared_ptr stützt sich im Grunde darauf weak_ptr.

T kann dann in seinen Methoden aufrufen shared_from_this um eine Instanz von zu erhalten shared_ptr<T> das auf den gleichen intern gespeicherten Referenzzählwert zurück. So haben Sie immer einen Ort wo T*Der Referenzzähler von wird gespeichert, anstatt mehrere zu haben shared_ptr Instanzen, die nichts voneinander wissen, und jeder denkt, dass sie das sind shared_ptr das ist für die Ref-Zählung zuständig T und löschen, wenn ihr Ref-Zähler Null erreicht.

  • Das ist richtig, und der wirklich wichtige Teil ist So, when you first create... denn das ist ein Erfordernis (Wie Sie sagen, wird der Weak_ptr nicht initialisiert, bis Sie den Objektzeiger an einen Shared_ptr-Ctor übergeben!) und bei dieser Anforderung können die Dinge schrecklich schief gehen, wenn Sie nicht aufpassen. Wenn Sie vor dem Aufruf keinen shared_ptr erstellen shared_from_this Sie erhalten UB – ebenso erhalten Sie UB, wenn Sie mehr als einen shared_ptr erstellen. Sie müssen irgendwie sicherstellen, dass Sie einen shared_ptr erstellen exakt Einmal.

    – AnorZaken

    1. September 2016 um 16:12 Uhr


  • Mit anderen Worten, die ganze Idee von enable_shared_from_thisist zunächst spröde, da es darauf ankommt, a zu bekommen shared_ptr<T> von einem T*aber in Wirklichkeit, wenn Sie einen Zeiger erhalten T* t Es ist im Allgemeinen nicht sicher, etwas darüber anzunehmen, dass es bereits geteilt wurde oder nicht, und die falsche Vermutung ist UB.

    – AnorZaken

    1. September 2016 um 16:17 Uhr

  • internal schwacher_ptr wird mit einem refcount von 1 initialisiert„Schwache PTR an T besitzen keine intelligenten PTR an T. Ein schwacher PTR ist ein besitzender intelligenter Verweis auf genügend Informationen, um einen besitzenden PTR zu erstellen, der eine „Kopie“ eines anderen besitzenden PTR ist. Ein schwacher PTR hat keine Verweisanzahl. Er hat Zugriff auf eine Ref-Zählung, wie alle besitzenden Ref.

    – Neugieriger

    28. November 2017 um 23:43 Uhr

Was ist die Nutzlichkeit von enable shared from this
Elad Maimoni

Es gibt einen besonderen Fall, wo ich finde enable_shared_from_this extrem nützlich: Thread-Sicherheit bei Verwendung von asynchronem Callback.

Klasse vorstellen Client hat ein Mitglied vom Typ AsynchronousPeriodicTimer:

struct AsynchronousPeriodicTimer
{
    // call this periodically on some thread...
    void SetCallback(std::function<void(void)> callback); 
    void ClearCallback(); // clears the callback
}

struct Client
{
    Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
        : _timer(timer)

    {
        _timer->SetCallback(
            [this]
            () 
            {
                assert(this); // what if 'this' is already dead because ~Client() has been called?
                std::cout << ++_counter << '\n';
            }
            );
    }
    ~Client()
    {
        // clearing the callback is not in sync with the timer, and can actually occur while the callback code is running
        _timer->ClearCallback();
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

int main()
{
    auto timer = std::make_shared<AsynchronousPeriodicTimer>();
    {
        auto client = std::make_shared<Client>(timer);
        // .. some code    
        // client dies here, there is a race between the client callback and the client destructor           
    }
}

Die Client-Klasse abonniert eine Callback-Funktion für den periodischen Zeitgeber. Sobald das Client-Objekt den Gültigkeitsbereich verlässt, gibt es eine Race-Bedingung zwischen dem Callback des Clients und dem Timer-Destruktor. Der Rückruf kann mit einem hängenden Zeiger aufgerufen werden!

Die Lösung: Mit enable_shared_from_this um die Lebensdauer des Objekts für die Dauer des Callback-Aufrufs zu verlängern.

struct Client : std::enable_shared_from_this<Client>
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer) 
    : _timer(timer)

    {

    }

    void Init()
    {
        auto captured_self = weak_from_this(); // weak_ptr to avoid cyclic references with shared_ptr

        _timer->SetCallback(
        [captured_self]
        () 
        {
            if (auto self = captured_self.lock())
            {
                // 'this' is guaranteed to be non-nullptr. we managed to promote captured_self to a shared_ptr           
                std::cout << ++self->_counter << '\n';
            }

        }
        );
    }
    ~Client()
    {
        // the destructor cannot be called while the callback is running. shared_ptr guarantees this
        _timer->ClearCallback();
    
    }
    int _counter = 0;
    std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}

Der Mechanismus von enable_shared_from_thiskombiniert mit der inhärenten Fadensicherheit von std::shared_ptr Referenzzählung, ermöglichen uns sicherzustellen, dass die Client -Objekt kann nicht zerstört werden, während der Callback-Code auf seine internen Mitglieder zugreift.

Notiere dass der Init Die Methode ist seit dem Initialisierungsprozess von vom Konstruktor getrennt enable_shared_from_this wird erst abgeschlossen, wenn der Konstruktor beendet wird. Daher die zusätzliche Methode. Es ist im Allgemeinen unsicher, einen asynchronen Rückruf innerhalb eines Konstruktors zu abonnieren, da der Rückruf möglicherweise auf nicht initialisierte Felder zugreift.

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

  • Ja aber enable_shared_from_this ermöglicht es Ihnen, mit einer API zu arbeiten, die speziell akzeptiert shared_ptr<>. Meiner Meinung nach ist eine solche API normalerweise Es falsch machen (da es besser ist, etwas Höheres im Stack den Speicher besitzen zu lassen), aber wenn Sie gezwungen sind, mit einer solchen API zu arbeiten, ist dies eine gute Option.

    – cdunn2001

    5. Juni 2013 um 14:55 Uhr


  • Bleiben Sie besser so weit wie möglich im Standard.

    – Sergej

    27. Februar 2019 um 0:15 Uhr


1646919015 132 Was ist die Nutzlichkeit von enable shared from this
mchiasson

In c++11 und höher ist es genau dasselbe: Es soll die Fähigkeit zur Rückkehr ermöglichen this als gemeinsam genutzter Zeiger seit this gibt Ihnen einen rohen Zeiger.

Mit anderen Worten, es erlaubt Ihnen, Code wie diesen zu drehen

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

private:

    Node * m_parent = nullptr;
};           

das mögen:

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

  • Ja aber enable_shared_from_this ermöglicht es Ihnen, mit einer API zu arbeiten, die speziell akzeptiert shared_ptr<>. Meiner Meinung nach ist eine solche API normalerweise Es falsch machen (da es besser ist, etwas Höheres im Stack den Speicher besitzen zu lassen), aber wenn Sie gezwungen sind, mit einer solchen API zu arbeiten, ist dies eine gute Option.

    – cdunn2001

    5. Juni 2013 um 14:55 Uhr


  • Bleiben Sie besser so weit wie möglich im Standard.

    – Sergej

    27. Februar 2019 um 0:15 Uhr


988090cookie-checkWas ist die Nützlichkeit von `enable_shared_from_this`?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy