std :: shared_ptr de ceci

101

J'essaie actuellement d'apprendre à utiliser des pointeurs intelligents. Cependant, en faisant quelques expériences, j'ai découvert la situation suivante pour laquelle je ne pouvais pas trouver de solution satisfaisante:

Imaginez que vous ayez un objet de classe A étant parent d'un objet de classe B (l'enfant), mais que les deux devraient se connaître:

class A;
class B;

class A
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children->push_back(child);

        // How to do pass the pointer correctly?
        // child->setParent(this);  // wrong
        //                  ^^^^
    }

private:        
    std::list<std::shared_ptr<B>> children;
};

class B
{
public:
    setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    };

private:
    std::shared_ptr<A> parent;
};

La question est de savoir comment un objet de classe A peut-il passer un std::shared_ptrde lui-même ( this) à son enfant?

Il existe des solutions pour les pointeurs partagés Boost ( Obtenir un boost::shared_ptrforthis ), mais comment gérer cela en utilisant les std::pointeurs intelligents?

Icare
la source
2
Comme pour tout autre outil, vous devez l'utiliser lorsque cela est approprié. Utiliser des pointeurs intelligents pour ce que vous faites n'est pas
YePhIcK
De même pour booster. Regardez ici .
juanchopanza
1
C'est un problème à ce niveau d'abstraction. Vous ne savez même pas que "ceci" pointe vers la mémoire sur le tas.
Vaughn Cato
Eh bien, la langue ne l'est pas, mais vous le faites. Tant que vous gardez une trace de ce qui est où, tout ira bien.
Alex le

Réponses:

168

Il y a std::enable_shared_from_thisjuste pour cela. Vous en héritez et vous pouvez appeler .shared_from_this()depuis l'intérieur de la classe. De plus, vous créez ici des dépendances circulaires qui peuvent entraîner des fuites de ressources. Cela peut être résolu avec l'utilisation de std::weak_ptr. Donc, votre code pourrait ressembler à ceci (en supposant que les enfants dépendent de l'existence du parent et non l'inverse):

class A;
class B;

class A
    : public std::enable_shared_from_this<A>
{
public:
    void addChild(std::shared_ptr<B> child)
    {
        children.push_back(child);

        // like this
        child->setParent(shared_from_this());  // ok
        //               ^^^^^^^^^^^^^^^^^^
    }

private:     
    // note weak_ptr   
    std::list<std::weak_ptr<B>> children;
    //             ^^^^^^^^
};

class B
{
public:
    void setParent(std::shared_ptr<A> parent)
    {
        this->parent = parent;
    }

private:
    std::shared_ptr<A> parent;
};

Notez cependant que l'appel .shared_from_this()requiert qu'il thisappartienne std::shared_ptrau point d'appel. Cela signifie que vous ne pouvez plus créer un tel objet sur la pile et que vous ne pouvez généralement pas appeler .shared_from_this()depuis un constructeur ou un destructeur.

yuri kilochek
la source
1
Merci pour votre explication et pour avoir signalé mon problème de dépendance circulaire.
Icarus
@Deduplicator que voulez-vous dire?
yuri kilochek
Essayez de construire un shared_ptrbasé sur une construction par défaut shared_ptret ce que vous voulez pointer vers ...
Deduplicator
1
@Deduplicator c'est, pardonnez mon jeu de mots, un pointeur partagé plutôt inutile. Ce constructeur est destiné à être utilisé avec des pointeurs vers des membres de l'objet géré ou de ses bases. En tout cas quel est votre point (je suis désolé)? Ces non-propriétaires shared_ptrne sont pas pertinents pour cette question. shared_from_thisLes conditions préalables stipulent clairement que l 'objet doit être possédé (et non simplement indiqué) par certains shared_ptrau moment de l' appel.
yuri kilochek
1
@kazarey La propriété par a shared_ptrest requise au moment de l'appel, mais dans un modèle d'utilisation typique, c'est-à-dire quelque chose comme shared_ptr<Foo> p(new Foo());, shared_ptrassume la propriété de l'objet seulement après sa construction complète. Il est possible de contourner cela en créant shared_ptrdans le constructeur initialisé avec thiset en le stockant quelque part non local (par exemple dans un argument de référence) pour qu'il ne meure pas lorsque le constructeur se termine. Mais ce scénario alambiqué ne sera probablement pas nécessaire.
yuri kilochek
9

Vous avez plusieurs problèmes dans votre conception, qui semblent provenir de votre mauvaise compréhension des pointeurs intelligents.

Les pointeurs intelligents sont utilisés pour déclarer la propriété. Vous brisez cela en déclarant que les deux parents possèdent tous les enfants, mais aussi que chaque enfant possède son parent. Les deux ne peuvent pas être vrais.

En outre, vous renvoyez un pointeur faible dans getChild(). Ce faisant, vous déclarez que l'appelant ne devrait pas se soucier de la propriété. Maintenant, cela peut être très limitant, mais aussi ce faisant, vous devez vous assurer que l'enfant en question ne sera pas détruit tant que des pointeurs faibles sont toujours détenus, si vous utilisiez un pointeur intelligent, il serait trié par lui-même .

Et la dernière chose. Habituellement, lorsque vous acceptez de nouvelles entités, vous devez généralement accepter des pointeurs bruts. Le pointeur intelligent peut avoir sa propre signification pour échanger des enfants entre parents, mais pour un usage général, vous devez accepter les pointeurs bruts.

Šimon Tóth
la source
Il semble que j'ai vraiment besoin de clarifier ma compréhension des pointeurs intelligents. Merci d'avoir fait remarquer cela.
Icarus