Réponses:
Il vous permet d'obtenir une shared_ptr
instance valide this
lorsque tout ce que vous avez est this
. Sans cela, vous n'auriez aucun moyen d'obtenir un accès shared_ptr
à this
moins 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_this
est devenu une partie de la norme C ++ 11. Vous pouvez également l'obtenir à partir de là ainsi que de boost.
std::shared_ptr
constructeur 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_ptr
pour un objet qui est déjà géré par un autre std::shared_ptr
ne 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_ptr
objets 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_ptr
objet 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_pt
objet r sp1
possède la nouvelle ressource allouée. Le code à l'intérieur de la fonction membre S::dangerous
ne connaît pas cet shared_ptr
objet, donc l' shared_ptr
objet qu'il renvoie est distinct de sp1
. Copier le nouvel shared_ptr
objet dans sp2
n'aide pas; quand sp2
sort de la portée, il libérera la ressource, et quand sp1
sortira 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_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
}
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 à T
laquelle 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_ptr
essentiellement sur ce point weak_ptr
.
T
peut alors, dans ses méthodes, appeler shared_from_this
pour 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_ptr
instances qui ne se connaissent pas, et chacun pense shared_ptr
que c'est lui qui est responsable du décompte T
et 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_this
vous 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_this
est 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* t
il 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_this
vous 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 this
tant que pointeur partagé car cela this
vous 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>::get
pour 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_stub
membre 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_deleter
un 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, this
sera supprimée et toutes les références suivantes seront pendantes.
enable_shared_from_this
, il conserve un weak_ptr
de lui-même (rempli par le ctor), retourné comme un shared_ptr
lorsque vous appelez shared_from_this
. En d'autres termes, vous dupliquez ce enable_shared_from_this
qui fournit déjà.