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?
268
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?
Réponses:
Un bon exemple serait un cache.
Pour les objets récemment accédés, vous souhaitez les conserver en mémoire, vous devez donc maintenir un pointeur fort sur eux. Périodiquement, vous analysez le cache et décidez quels objets n'ont pas été consulté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 vers lui? Si le cache se débarrasse de son seul pointeur sur l'objet, il ne pourra plus jamais le retrouver. Ainsi, le cache conserve un pointeur faible vers les objets dont il a besoin pour 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.
la source
std::weak_ptr
est un très bon moyen de résoudre le problème du pointeur pendant . 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 unstd::shared_ptr
gérer les données et en fournissantstd::weak_ptr
aux utilisateurs des données, les utilisateurs peuvent vérifier la validité des données en appelantexpired()
oulock()
.Vous ne pouvez pas le faire
std::shared_ptr
seul, car toutes lesstd::shared_ptr
instances partagent la propriété des données qui ne sont pas supprimées avant que toutes les instances destd::shared_ptr
soient supprimées. Voici un exemple de vérification du pointeur pendant à l'aide delock()
:la source
std::weak_ptr::lock
crée un nouveaustd::shared_ptr
partage de la propriété de l'objet géré.Une autre réponse, espérons-le plus simple. (pour les autres googleurs)
Supposons que vous avez
Team
etMember
objets.Évidemment, c'est une relation: l'
Team
objet aura des pointeurs vers sonMembers
. Et il est probable que les membres auront également un pointeur arrière sur leurTeam
objet.Ensuite, vous avez un cycle de dépendance. Si vous utilisez
shared_ptr
, les objets ne seront plus automatiquement libérés lorsque vous abandonnerez la référence sur eux, car ils se référencent de manière cyclique. Il s'agit d'une fuite de mémoire.Vous cassez cela en utilisant
weak_ptr
. Le «propriétaire» utilise généralementshared_ptr
et le «propriétaire» utilise aweak_ptr
pour son parent, et le convertit temporairement aushared_ptr
moment où il a besoin d'accéder à son parent.Stocker un ptr faible:
puis utilisez-le en cas de besoin
la source
shared_ptr
est de partager la propriété, donc personne n'a la responsabilité particulière de libérer la mémoire, elle est libérée automatiquement lorsqu'elle n'est plus utilisée. A moins qu'il n'y ait une boucle ... Vous pouvez avoir plusieurs équipes partageant le même joueur (anciennes équipes?). Si l'objet d'équipe "possède" les membres, il n'est pas nécessaire d'utiliser unshared_ptr
pour commencer.shared_ptr
référencée par ses «membres de l'équipe», quand sera-t-elle détruite? Ce que vous décrivez est un cas où il n'y a pas de boucle.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 vouloir faire quelque chose avec ces tâches périodiquement, donc un événement de minuterie peut traverser unstd::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'elle n'était plus nécessaire et mourir. Le temporisateur peut ainsi vérifier si la tâche est toujours en vie en créant un pointeur partagé à partir du pointeur faible et en utilisant ce pointeur partagé, à condition qu'il ne soit pas nul.la source
Ils sont utiles avec Boost.Asio lorsque vous n'êtes pas assuré qu'un objet cible existe toujours lorsqu'un gestionnaire asynchrone est appelé. L'astuce consiste à lier un
weak_ptr
dans l'objet gestionnaire asynchrone, à l'aide destd::bind
captures lambda ou.Il s'agit d'une variante de l'
self = shared_from_this()
idiome souvent vu dans les exemples Boost.Asio, où un gestionnaire asynchrone en attente ne prolonge pas la durée de vie de l'objet cible, mais est toujours sûr si l'objet cible est supprimé.la source
this
self = shared_from_this()
idiome lorsque le gestionnaire invoque des méthodes au sein de la même classe.shared_ptr : contient l'objet réel.
faiblesse_ptr : utilise
lock
pour se connecter au vrai propriétaire ou renvoie un NULLshared_ptr
sinon.En gros, le
weak_ptr
rôle est similaire à celui de l' agence de logement . Sans agents, pour obtenir une maison en location, nous devrons peut-être 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 à la location.la source
weak_ptr
est également bon pour vérifier la suppression correcte d'un objet - en particulier dans les tests unitaires. Le cas d'utilisation typique pourrait ressembler à ceci:la source
Lors de l'utilisation de pointeurs, il est important de comprendre les différents types de pointeurs disponibles et quand il est logique de les utiliser. Il existe quatre types de pointeurs dans deux catégories, comme suit:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
]
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
]
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
Les pointeurs bruts (parfois appelés «pointeurs hérités» ou «pointeurs C») fournissent un comportement de pointeur «à nu» et sont une source courante de bogues 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 manuellement «supprimer» 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 doivent généralement être évités et uniquement utilisés dans les sections critiques du code de performance avec une portée limitée.
Les pointeurs uniques sont un pointeur intelligent de base qui «possède» le pointeur brut sous-jacent vers la ressource et est responsable de l'appel de suppression et de la libération de la mémoire allouée une fois que l'objet «propriétaire» du 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 vers 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 évite au développeur 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é du pointeur partagé. Comme le pointeur unique, les pointeurs partagés sont responsables de la libération de la mémoire allouée une fois que tous les objets ont terminé de pointer vers la ressource. Il accomplit cela avec une technique appelée comptage de références. Chaque fois qu'un nouvel objet s'approprie le pointeur partagé, le nombre de références est incrémenté de 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é de 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é chaque fois que 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, ils pointent vers un autre pointeur (faible ou partagé). Les pointeurs faibles ne peuvent pas accéder directement à un objet, mais ils peuvent dire si l'objet existe toujours ou s'il a expiré. Un pointeur faible peut être temporairement converti en un pointeur partagé pour accéder à l'objet pointé (à condition qu'il existe toujours). Pour illustrer, considérons l'exemple suivant:
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 afin qu'il puisse se terminer sans vous, et vous ne savez pas s'il s'est terminé ou non, sauf si vous cochez. S'il n'est pas terminé, vous pouvez vous inscrire et participer, sinon, vous ne pouvez pas. Cela est différent que d'avoir un pointeur partagé vers la réunion B car vous seriez alors un "propriétaire" à la fois dans la réunion A et la réunion B (participant aux deux en même temps).
L'exemple illustre comment un pointeur faible fonctionne et est utile lorsqu'un objet doit être un observateur extérieur , mais ne veut pas la responsabilité de partager la propriété. Ceci est particulièrement utile dans le scénario où deux objets doivent pointer l'un vers l'autre (c'est-à-dire une référence circulaire). Avec des pointeurs partagés, aucun objet ne peut être libéré car ils sont toujours "fortement" pointés par l'autre objet. Lorsque l'un des pointeurs est un pointeur faible, l'objet contenant le pointeur faible peut toujours accéder à l'autre objet si nécessaire, à condition qu'il existe toujours.
la source
Outre les autres cas d'utilisation valides déjà mentionnés,
std::weak_ptr
c'est un outil génial dans un environnement multithread, carstd::shared_ptr
en conjonction avecstd::weak_ptr
est sûr contre les pointeurs pendants - contrairement àstd::unique_ptr
en conjonction avec les pointeurs brutsstd::weak_ptr::lock()
est une opération atomique (voir aussi À propos de la sécurité des threads de faiblesse_ptr )Envisagez une tâche pour charger simultanément toutes les images d'un répertoire (~ 10 000) en mémoire (par exemple, sous forme de cache de vignettes). De toute évidence, la meilleure façon de procéder est un thread de contrôle, qui gère et gère les images, et plusieurs threads de travail, qui chargent les images. Maintenant, c'est une tâche facile. Voici une implémentation très simplifiée (
join()
etc est omis, les threads devraient être traités différemment dans une implémentation réelle, etc.)Mais cela devient beaucoup plus compliqué, si vous voulez interrompre le chargement des images, par exemple parce que l'utilisateur a choisi un répertoire différent. Ou même si vous voulez détruire le manager.
Vous auriez besoin d'une communication de thread et devez arrêter tous les threads du chargeur, avant de pouvoir modifier votre
m_imageDatas
champ. Sinon, les chargeurs continueraient de charger jusqu'à ce que toutes les images soient terminées - même si elles sont déjà obsolètes. Dans l'exemple simplifié, ce ne serait pas trop difficile, mais dans un environnement réel, les choses peuvent être beaucoup plus compliquées.Les threads feraient probablement partie d'un pool de threads utilisé par plusieurs gestionnaires, dont certains sont arrêtés, d'autres non, etc. Le paramètre simple
imagesToLoad
serait une file d'attente verrouillée, dans laquelle ces gestionnaires poussent leurs demandes d'image à partir de différents threads de contrôle. avec les lecteurs sautant les demandes - dans un ordre arbitraire - à l'autre bout. Et donc la communication devient difficile, lente et sujette aux erreurs. Une manière très élégante d'éviter toute communication supplémentaire dans de tels cas est d'utiliserstd::shared_ptr
en conjonction avecstd::weak_ptr
.Cette implémentation est presque aussi simple que la première, ne nécessite aucune communication de thread supplémentaire et pourrait faire partie d'un pool / file d'attente de threads dans une implémentation réelle. Étant donné que les images expirées sont ignorées et que les images non expirées sont traitées, les threads ne devraient jamais être arrêtés pendant le fonctionnement normal. Vous pouvez toujours changer le chemin en toute sécurité ou détruire vos gestionnaires, car le lecteur fn vérifie si le pointeur propriétaire n'est pas expiré.
la source
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: faiblesse_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 en std :: shared_ptr pour accéder à l'objet référencé.
std :: faible_ptr modélise la propriété temporaire: lorsqu'un objet n'a besoin d'être accédé que s'il existe, et qu'il peut être supprimé à tout moment par quelqu'un d'autre, std :: faible_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 d'origine 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 :: faible_ptr est utilisé pour casser les références circulaires de std :: shared_ptr.
la source
Il y a un inconvénient du pointeur partagé: shared_pointer ne peut pas gérer la dépendance du cycle parent-enfant. Signifie si la classe parent utilise l'objet de la classe enfant à l'aide d'un pointeur partagé, dans le même fichier si la classe enfant utilise l'objet de la classe parent. 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. le pointeur fondamentalement partagé ne prend pas en charge le mécanisme de comptage de référence.
Cet inconvénient, nous pouvons le surmonter en utilisant le pointeur faible.
la source
weak_ptr
gérer une dépendance circulaire sans changement dans la logique du programme en remplacement du remplacementshared_ptr
?" :-)Lorsque nous ne voulons pas posséder l'objet:
Ex:
Dans la classe ci-dessus, wPtr1 ne possède pas la ressource pointée par wPtr1. Si la ressource est supprimée, wPtr1 a expiré.
Pour éviter une dépendance circulaire:
Maintenant, si nous faisons le shared_ptr de la classe B et A, le use_count des deux pointeurs est deux.
Lorsque le shared_ptr sort de la portée de l'OD, le nombre reste toujours 1 et donc les objets A et B ne sont pas supprimés.
production:
Comme nous pouvons le voir à la sortie, 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 faibles_ptr dans la classe A au lieu de shared_ptr qui a plus de sens.
la source
Je vois
std::weak_ptr<T>
comme une poignée à unstd::shared_ptr<T>
: il me permet d'obtenir lestd::shared_ptr<T>
s'il existe toujours, mais il ne prolongera pas sa durée de vie. Il existe plusieurs scénarios où un tel point de vue est utile:Un autre scénario important consiste à briser les cycles des structures de données.
Herb Sutter a un excellent discours qui explique la meilleure utilisation des fonctionnalités du langage (dans ce cas, des pointeurs intelligents) pour assurer la liberté de fuite par défaut (ce qui signifie: tout clique en place par construction; vous pouvez à peine le visser). C'est une montre incontournable.
la source