Réponses:
Il vous permet d'obtenir une shared_ptrinstance valide thislorsque tout ce que vous avez est this. Sans cela, vous n'auriez aucun moyen d'obtenir un accès shared_ptrà thismoins que vous n'en ayez déjà un en tant que membre. Cet exemple 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 une valeur valide shared_ptr, même si elle n'avait 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é que cela a renvoyé aura un nombre de références différent du "bon", et l'un d'eux finira par perdre et conserver une référence pendant lorsque l'objet est supprimé.
enable_shared_from_thisest devenu une partie de la norme C ++ 11. Vous pouvez également l'obtenir à partir de là ainsi que de boost.
std::shared_ptrconstructeur sur un pointeur brut s'il hérite de std::enable_shared_from_this. Je ne sais pas si la sémantique de Boost a été mise à jour pour supporter cela.
std::shared_ptrpour un objet qui est déjà géré par un autre std::shared_ptrne consultera pas la référence faible stockée en interne et entraînera donc un comportement indéfini." ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p?
std::make_shared<T>.
d'après 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_ptrobjets ne connaît l'autre, donc les deux essaieront de libérer la ressource quand ils seront détruits. Cela entraîne généralement des problèmes.
De même, si une fonction membre a besoin d'un shared_ptrobjet qui possède l'objet auquel elle est appelée, elle 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, bien que sous une forme plus subtile. Lorsqu'il est construit, l' shared_ptobjet r sp1possède la nouvelle ressource allouée. Le code à l'intérieur de la fonction membre S::dangerousne connaît pas cet shared_ptrobjet, donc l' shared_ptrobjet qu'il renvoie est distinct de sp1. Copier le nouvel shared_ptrobjet dans sp2n'aide pas; quand sp2sort de la portée, il libérera la ressource, et quand sp1sortira de la portée, il relâchera la ressource.
Pour éviter ce problème, utilisez le modèle de classe enable_shared_from_this. Le modèle prend un argument de type de 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 effectuez cette opération, gardez à l'esprit que l'objet sur lequel vous appelez shared_from_thisdoit appartenir à un shared_ptrobjet. Cela ne fonctionnera pas:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);cela, il peut être préférable d'utiliser shared_ptr<S> sp1 = make_shared<S>();, voir par exemple stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();car l'écueil ici est que vous devez créer un shared_ptr de la manière normale avant d'appeler shared_from_this()la première fois! C'est vraiment facile de se tromper! Avant C ++ 17, il est UB d'appeler shared_from_this()avant qu'exactement un shared_ptr ait été créé de façon normale: auto sptr = std::make_shared<S>();ou shared_ptr<S> sptr(new S());. Heureusement, à partir de C ++ 17, cela le fera.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();<- Il est permis d'appeler shared_from_this uniquement sur un objet précédemment partagé, c'est-à-dire sur un objet géré par std :: shared_ptr <T>. Sinon, le comportement n'est pas défini (jusqu'à C ++ 17) std :: bad_weak_ptr est levé (par le constructeur shared_ptr à partir d'une faiblesse construite par défaut) (depuis C ++ 17). . Donc, la réalité est qu'il faut l'appeler always_dangerous(), car vous avez besoin de savoir si elle a déjà été partagée ou non.
Voici mon explication, du point de vue des écrous et boulons (la réponse du haut n'a pas «cliqué» avec moi). * Notez que cela est le résultat de la recherche de la source de 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 weak_ptr<T>instance privée à Tlaquelle détient le « un vrai nombre de références » pour l'instance de T.
Ainsi, lorsque vous créez pour la première fois un shared_ptr<T>sur un nouveau T *, le faiblesse_ptr interne de ce T * est initialisé avec un décompte de 1. Le nouveau revient shared_ptressentiellement sur ce point weak_ptr.
Tpeut alors, dans ses méthodes, appeler shared_from_thispour obtenir une instance de shared_ptr<T>celle-ci sur le même compte de référence stocké en interne . De cette façon, vous avez toujours un endroit où T*le décompte de ref est stocké plutôt que d'avoir plusieurs shared_ptrinstances qui ne se connaissent pas, et chacun pense shared_ptrque c'est lui qui est responsable du décompte Tet de le supprimer lorsque leur ref -le nombre atteint zéro.
So, when you first create...parce que c'est une exigence (comme vous le dites, le faiblesse_ptr n'est pas initialisé jusqu'à ce que vous passiez le pointeur des objets dans un ctor shared_ptr!) Et cette exigence est l'endroit où les choses peuvent aller horriblement mal si vous êtes pas prudent. Si vous ne créez aucun shared_ptr avant d'appeler, shared_from_thisvous obtenez UB - de même si vous créez plus d'un shared_ptr, vous obtenez également UB. Vous devez en quelque sorte vous assurer de créer un shared_ptr exactement une fois.
enable_shared_from_thisest fragile au départ, car il s'agit de pouvoir obtenir un shared_ptr<T>de a T*, mais en réalité, lorsque vous obtenez un pointeur, T* til n'est généralement pas sûr de supposer que quelque chose est déjà partagé ou non, et faire la mauvaise supposition est UB.
Notez que l'utilisation d'un boost :: intrusive_ptr ne souffre pas de ce problème. C'est souvent un moyen plus pratique de contourner ce problème.
enable_shared_from_thisvous permet de travailler avec une API qui accepte spécifiquement shared_ptr<>. À mon avis, une telle API est généralement Doing It Wrong (car il vaut mieux laisser quelque chose de plus élevé dans la pile posséder la mémoire) mais si vous êtes obligé de travailler avec une telle API, c'est une bonne option.
C'est exactement la même chose en c ++ 11 et versions ultérieures: c'est pour permettre la possibilité de revenir en thistant que pointeur partagé car cela thisvous donne un pointeur brut.
en d'autres termes, cela vous permet de transformer du code comme celui-ci
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
en cela:
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;
};
shared_ptr. Vous voudrez peut-être changer l'interface pour vous assurer que c'est le cas.
std::shared_ptr<Node> getParent const(), je l'exposerais normalement comme à la NodePtr getParent const()place. Si vous avez absolument besoin d'accéder au pointeur brut interne (meilleur exemple: traiter avec une bibliothèque C), il y en a std::shared_ptr<T>::getpour cela, que je déteste mentionner parce que j'ai cet accesseur de pointeur brut utilisé trop de fois pour la mauvaise raison.
Une autre façon consiste à ajouter un weak_ptr<Y> m_stubmembre dans le class Y. Puis écrire:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Utile lorsque vous ne pouvez pas changer la classe dont vous dérivez (par exemple, étendre la bibliothèque d'autres personnes). N'oubliez pas d'initialiser le membre, par exemple par m_stub = shared_ptr<Y>(this), il est valide même pendant un constructeur.
C'est OK s'il y a plus de stubs comme celui-ci dans la hiérarchie d'héritage, cela n'empêchera pas la destruction de l'objet.
Edit: Comme l'a correctement souligné l'utilisateur nobar, le code détruirait l'objet Y lorsque l'affectation est terminée et les variables temporaires sont détruites. Par conséquent, ma réponse est incorrecte.
shared_ptr<>qui ne supprime pas sa pointe, c'est exagéré. Vous pouvez simplement dire return shared_ptr<Y>(this, no_op_deleter);où no_op_deleterun objet de fonction unaire prend Y*et ne fait rien.
m_stub = shared_ptr<Y>(this)va construire et détruire immédiatement un shared_ptr temporaire à partir de cela. Lorsque cette instruction est terminée, thissera supprimée et toutes les références suivantes seront pendantes.
enable_shared_from_this, il conserve un weak_ptrde lui-même (rempli par le ctor), retourné comme un shared_ptrlorsque vous appelez shared_from_this. En d'autres termes, vous dupliquez ce enable_shared_from_thisqui fournit déjà.