web-dev-qa-db-fra.com

Quelle est l'utilité de `enable_shared_from_this`?

J'ai couru à travers enable_shared_from_this en lisant les exemples de Boost.Asio et après avoir lu la documentation, je suis encore perdu pour savoir comment cela devrait être utilisé correctement. Quelqu'un peut-il me donner s'il vous plaît un exemple et/ou une explication de l'utilisation de cette classe est logique.

327
fido

Il vous permet d’obtenir une instance valide shared_ptr À this alors que tout ce que vous avez est this. Sans cela, vous n'auriez aucun moyen d'obtenir un shared_ptr Vers this, à moins d'en avoir déjà un en tant que membre. Cet exemple tiré de la documentation de boost pour 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
}

La méthode f() renvoie un shared_ptr Valide, même si elle n'a pas d'instance membre. Notez que vous ne pouvez pas simplement faire ceci:

class Y: public enable_shared_from_this<Y>
{
public:

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

Le pointeur partagé renvoyé aura un nombre de références différent de celui "correct" et l'un d'eux finira par perdre et conserver une référence en suspens lorsque l'objet sera supprimé.

enable_shared_from_this Est devenu une partie de la norme C++ 11. Vous pouvez également l'obtenir à partir de là ainsi que de boost.

339
1800 INFORMATION

de l'article du Dr Dobbs sur les pointeurs faibles, je pense que cet exemple est plus facile à comprendre (source: http://drdobbs.com/cpp/184402026 ):

... Un code comme celui-ci ne fonctionnera pas correctement:

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

Aucun des deux shared_ptr Les objets connaissent l'autre, alors les deux essaient de libérer la ressource lorsqu'ils sont détruits. Cela conduit généralement à des problèmes.

De même, si une fonction membre a besoin d'un shared_ptr objet qui possède l’objet sur lequel il est appelé, il ne peut pas simplement créer un objet à la volée:

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

Ce code a le même problème que l'exemple précédent, mais sous une forme plus subtile. Quand il est construit, le shared_ptr objet sp1 possède la nouvelle ressource allouée. Le code à l'intérieur de la fonction membre S::dangerous ne sait pas à ce sujet shared_ptr objet, donc le shared_ptr l'objet renvoyé est distinct de sp1. Copier le nouveau shared_ptr objet à sp2 n'aide pas; quand sp2 sort du cadre, la ressource sera libérée, et quand sp1 sort du cadre, la ressource sera libérée à nouveau.

Le moyen d'éviter ce problème consiste à utiliser le modèle de classe enable_shared_from_this. Le modèle prend un argument de type modèle, qui est le nom de la classe qui définit la ressource gérée. Cette classe doit à son tour être dérivée publiquement du modèle; comme ça:

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

Lorsque vous faites cela, gardez à l'esprit que l'objet sur lequel vous appelez shared_from_this doit appartenir à un shared_ptr objet. Cela ne fonctionnera pas:

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

Voici mon explication, du point de vue des détails (la réponse du haut n’a pas "cliqué" avec moi). * Notez que ceci est le résultat d'une recherche de la source pour shared_ptr et enable_shared_from_this fourni avec Visual Studio 2012. Peut-être que d'autres compilateurs implémentent enable_shared_from_this différemment ... *

enable_shared_from_this<T> Ajoute une instance privée weak_ptr<T> À T qui contient le 'n compte de références vraies' pour l'instance de T.

Ainsi, lorsque vous créez pour la première fois un shared_ptr<T> Sur un nouveau T *, le fichier faible_ptr interne de ce dernier est initialisé avec un nombre de référence égal à 1. Le nouveau shared_ptr Est essentiellement basé sur ce weak_ptr.

T peut alors, dans ses méthodes, appeler shared_from_this pour obtenir une instance de shared_ptr<T> qui retourne sur le même nombre de références stockées en interne. De cette façon, vous avez toujours un endroit où le nombre de références de T* Est stocké au lieu d'avoir plusieurs instances shared_ptr Qui ne se connaissent pas et chacune pense qu'elles sont les shared_ptr Responsable du comptage en parallèle T et de le supprimer lorsque leur compte en référence atteint zéro.

27
mackenir

Notez que l'utilisation d'un boost :: intrusive_ptr ne souffre pas de ce problème. C'est souvent un moyen plus pratique de résoudre ce problème.

3
blais

C'est exactement la même chose dans c ++ 11 et les versions ultérieures: cela permet d'activer la possibilité de retourner this en tant que pointeur partagé puisque this vous donne un pointeur brut.

dans d'autres mots, il vous permet de transformer le code comme ceci

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

private:

    Node * m_parent = nullptr;
};           

dans ceci:

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