Quand std:: weak ptr est-il utile?
J'ai commencé à étudier les pointeurs intelligents de C++11 et je ne vois aucune utilisation utile de std::weak_ptr
. Quelqu'un peut-il me dire quand std::weak_ptr
est utile/nécessaire?
11 réponses
Un bon exemple serait un cache.
Pour les objets récemment accédés, vous voulez les garder en mémoire, de sorte que vous maintenez un pointeur fort vers eux. Périodiquement, vous analysez le cache et décidez quels objets n'ont pas été accédés récemment. Vous n'avez pas besoin de les garder en mémoire, donc vous vous débarrassez du pointeur fort.
Mais que se passe-t-il si cet objet est utilisé et qu'un autre code contient un pointeur fort? Si le cache se débarrasse de son seul pointeur vers l'objet, il ne peut jamais trouver Encore une fois. Ainsi, le cache conserve un pointeur faible sur les objets qu'il doit trouver s'ils restent en mémoire.
C'est exactement ce que fait un pointeur faible-il vous permet de localiser un objet s'il est toujours là, mais ne le garde pas si rien d'autre n'en a besoin.
std::weak_ptr
est une très bonne façon de résoudre le , balançant pointeur problème. En utilisant simplement des pointeurs bruts, il est impossible de savoir si les données référencées ont été désallouées ou non. Au lieu de cela, en laissant un std::shared_ptr
gérer les données et en fournissant std::weak_ptr
aux utilisateurs des données, les utilisateurs peuvent vérifier la validité des données en appelant expired()
ou lock()
.
Vous ne pouvez pas le faire avec std::shared_ptr
seul, car toutes les instances std::shared_ptr
partagent la propriété des données qui ne sont pas supprimées avant toutes les instances de std::shared_ptr
sont supprimés. Voici un exemple de la façon de vérifier le pointeur en utilisant lock()
:
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
Une autre réponse, espérons plus simple. (pour les autres googlers)
Supposons que vous ayez des objets Team
et Member
.
Évidemment, c'est une relation : l'objet Team
aura des pointeurs vers son Members
. Et il est probable que les membres auront également un pointeur arrière vers leur objet Team
.
Ensuite, vous avez un cycle de dépendance. Si vous utilisez shared_ptr
, les objets ne seront plus automatiquement libérés lorsque vous abandonnez la référence sur eux, car ils se référencent de manière cyclique. C'est une fuite de mémoire.
Vous cassez ceci en utilisant weak_ptr
. Le" propriétaire "utilise généralement shared_ptr
et le "propriétaire" utilise un weak_ptr
à son parent, et le convertit Temporairement enshared_ptr
lorsqu'il a besoin d'accéder à son parent.
Stocker un ptr faible:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
Ensuite, utilisez-le si nécessaire
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( not tempParentSharedPtr ) {
// yes it may failed if parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
Voici un exemple, donné par @jleahy: supposons que vous ayez une collection de tâches, exécutées de manière asynchrone, et gérées par un std::shared_ptr<Task>
. Vous pouvez faire quelque chose avec ces tâches périodiquement, donc un événement de minuterie peut traverser un std::vector<std::weak_ptr<Task>>
et donner aux tâches quelque chose à faire. Cependant, simultanément une tâche peut avoir simultanément décidé qu'il n'est plus nécessaire et mourir. La minuterie peut ainsi vérifier si la tâche est toujours vivante en faisant un pointeur partagé à partir du pointeur faible et en l'utilisant pointeur partagé, à condition qu'il ne soit pas null.
weak_ptr
est également bon de vérifier la suppression correcte d'un objet - en particulier dans les tests unitaires. Le cas d'utilisation typique peut ressembler à ceci:
std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
Shared_ptr : contient l'objet réel.
Weak_ptr : utilise lock
pour se connecter au propriétaire réel ou renvoie NULL sinon.
Grosso modo, le rôle de weak_ptr
est similaire au rôle de agence de logement . Sans agents, pour obtenir une maison sur le loyer, nous pourrions avoir à vérifier les maisons au hasard dans la ville. Les agents s'assurent que nous ne visitons que les maisons qui sont encore accessibles et disponibles à louer.
Ils sont utiles avec Boost.Asio lorsque vous n'êtes pas sûr qu'un objet cible existe toujours lorsqu'un gestionnaire asynchrone est appelé. L'astuce consiste à lier un weak_ptr
dans l'objet gestionnaire asynchrone, en utilisant des captures std::bind
ou lambda.
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak](const boost::system::error_code& ec)
{
auto self = weak.lock();
if (self)
{
self->handleTimeout();
}
else
{
std::cout << "Target object no longer exists!\n";
}
} );
}
C'est une variante de l'idiome self = shared_from_this()
souvent vu dans Boost.Asio exemples, où un gestionnaire asynchrone en attente Pas prolonger la durée de vie de l'objet cible, mais est toujours sûr si l'objet cible est supprimé.
Lorsque vous utilisez des pointeurs, il est important de comprendre les différents types de pointeurs disponibles et quand il est logique d'utiliser chacun d'eux. Il existe quatre types de pointeurs dans deux catégories comme suit:
- pointeurs bruts:
- pointeur brut [c'est-à-dire
SomeClass* ptrToSomeClass = new SomeClass();
]
- pointeur brut [c'est-à-dire
- pointeurs intelligents:
- pointeurs uniques [ c'est-à-dire
std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
] - pointeurs partagés [c'est-à-dire
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
] - pointeurs faibles [ c'est-à-dire
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
- pointeurs uniques [ c'est-à-dire
Pointeurs bruts (parfois appelés "pointeurs hérités", ou" pointeurs C") fournissent un comportement de pointeur "bare-bones" et sont une source commune de bugs et de fuites de mémoire. Les pointeurs bruts ne fournissent aucun moyen de garder une trace de la propriété de la ressource et les développeurs doivent appeler "delete" manuellement pour s'assurer qu'ils ne créent pas de fuite de mémoire. Cela devient difficile si la ressource est partagée car il peut être difficile de savoir si des objets pointent toujours vers la ressource. Pour ces raisons, les pointeurs bruts devraient généralement être évité et utilisé uniquement dans les sections critiques de performance du code avec une portée limitée.
Les pointeurs uniques sont un pointeur intelligent de base qui "possède" le pointeur brut sous-jacent à la ressource et est responsable de l'appel de delete et de la libération de la mémoire allouée une fois que l'objet qui "possède" le pointeur unique est hors de portée. Le nom "unique" fait référence au fait qu'un seul objet peut "posséder" le pointeur unique à un moment donné. La propriété peut être transférée à un autre objet via la commande move, mais un pointeur unique ne peut jamais être copié ou partagé. Pour ces raisons, les pointeurs uniques sont une bonne alternative aux pointeurs bruts dans le cas où un seul objet a besoin du pointeur à un moment donné, ce qui soulage le développeur de la nécessité de libérer de la mémoire à la fin du cycle de vie de l'objet propriétaire.
Les pointeurs partagés sont un autre type de pointeur intelligent qui sont similaires aux pointeurs uniques, mais permettent à de nombreux objets d'avoir la propriété sur le pointeur partagé. Comme pointeur unique, les pointeurs partagés sont responsables de libérer la mémoire allouée une fois que tous les objets pointent vers la ressource. Il accomplit cela avec une technique appelée comptage de référence. Chaque fois qu'un nouvel objet prend possession du pointeur partagé, le nombre de références est incrémenté d'un. De même, lorsqu'un objet sort de la portée ou cesse de pointer vers la ressource, le nombre de références est décrémenté d'un. Lorsque le nombre de références atteint zéro, la mémoire allouée est libérée. Pour ces raisons, les pointeurs partagés sont un type très puissant de pointeur intelligent qui doit être utilisé à tout moment plusieurs objets doivent pointer vers la même ressource.
Enfin, les pointeurs faibles sont un autre type de pointeur intelligent qui, plutôt que de pointer directement vers une ressource, pointe vers un autre pointeur (faible ou partagé). Les pointeurs faibles ne peuvent pas accéder directement à un objet, mais ils peuvent indiquer si l'objet existe toujours ou s'il a expiré. Un pointeur faible peut être temporairement converti en un smart pointeur pour accéder à l'objet pointé (à condition qu'il existe toujours). Pour illustrer, considérons l'exemple suivant:
- Vous êtes occupé et avez des réunions qui se chevauchent: Réunion A et réunion B
- vous décidez d'aller à La Réunion A et votre collègue va à La Réunion B
- vous dites à votre collègue que si la Réunion B continue après la fin de la réunion A, vous rejoindrez
- Les deux scénarios suivants pourraient jouer:
- La Réunion A se termine et La Réunion B continue, donc vous rejoignez
- La Réunion A se termine et La Réunion B est également terminée, donc vous ne Rejoignez pas
Dans l'exemple, vous avez un pointeur faible vers la Réunion B. Vous n'êtes pas un "propriétaire" dans la Réunion B, donc cela peut se terminer sans vous, et vous ne savez pas si cela s'est terminé ou non, sauf si vous vérifiez. Si elle n'a pas pris fin, vous pouvez rejoindre et participer, sinon, vous ne pouvez pas. Ceci est différent d'avoir un pointeur partagé vers la Réunion B car vous seriez alors un "propriétaire" dans la Réunion A et La Réunion B (participant tous les deux en même temps).
L'exemple illustre le fonctionnement d'un pointeur faible et est utile lorsqu'un objet doit être un observateur extérieur , mais ne veut pas être responsable de la propriété. Ceci est particulièrement utile dans le scénario où deux objets doivent pointer l'un vers l'autre (alias une référence circulaire). Avec les pointeurs partagés, aucun objet ne peut être libéré car ils sont toujours "fortement" pointés par l'autre objet. Avec des pointeurs faibles, les objets peut être consulté en cas de besoin, et libéré quand ils n'ont plus besoin d'exister.
Http://en.cppreference.com/w/cpp/memory/weak_ptr std:: weak_ptr est un pointeur intelligent qui contient une référence non propriétaire ("faible") à un objet géré par std::shared_ptr. Il doit être converti à std::shared_ptr pour accéder à l'objet référencé.
STD:: weak_ptr modèles propriété temporaire: lorsqu'un objet doit être accessible uniquement s'il existe, et qu'il peut être supprimé à tout moment par quelqu'un d'autre, std:: weak_ptr est utilisé pour suivre l'objet, et il est converti en std:: shared_ptr pour assumer la propriété temporaire. Si le std::shared_ptr original est détruit à ce moment, la durée de vie de l'objet est prolongée jusqu'à ce que le std::shared_ptr temporaire soit également détruit.
De plus, std:: weak_ptr est utilisé pour casser les références circulaires de std:: shared_ptr.
Il y a un inconvénient du pointeur partagé: shared_pointer ne peut pas gérer la dépendance du cycle parent-enfant. Signifie que si la classe parent utilise l'objet de la classe enfant à l'aide d'un pointeur partagé, dans le même fichier, si l'enfant utilise de l'objet de la classe parente. Le pointeur partagé ne parviendra pas à détruire tous les objets, même le pointeur partagé n'appelle pas du tout le destructeur dans le scénario de dépendance de cycle. fondamentalement pointeur partagé ne prend pas en charge le mécanisme de comptage de référence.
Ce inconvénient que nous pouvons surmonter en utilisant weak_pointer.
Lorsque nous ne voulons pas posséder l'objet:
Ex:
class A
{
shared_ptr<int> sPtr1;
weak_ptr<int> wPtr1;
}
Dans la classe ci-dessus, wPtr1 ne possède pas la ressource pointée par wPtr1. Si la ressource est supprimée, wPtr1 est expiré.
Pour éviter la dépendance circulaire:
shard_ptr<A> <----| shared_ptr<B> <------
^ | ^ |
| | | |
| | | |
| | | |
| | | |
class A | class B |
| | | |
| ------------ |
| |
-------------------------------------
Maintenant, si nous faisons le shared_ptr de la Classe B et A, le use_count du pointeur both est deux.
Lorsque le shared_ptr sort de la portée od, le nombre reste toujours 1 et donc l'objet a et B ne reçoit pas supprimer.
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
Sortie:
A()
B()
Comme nous pouvons le voir à partir de la sortie que les pointeurs A et B ne sont jamais supprimés et donc la fuite de mémoire.
Pour éviter un tel problème, utilisez simplement weak_ptr dans la classe A au lieu de shared_ptr, ce qui est plus logique.