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.
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 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 std::shared_ptr
seul, car toutes les std::shared_ptr
instances partagent la propriété des données qui ne sont pas supprimées avant que toutes les instances de std::shared_ptr
soient supprimées. Voici un exemple de vérification du pointeur pendant à l'aide de 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";
}
std::weak_ptr::lock
crée un nouveau std::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
et Member
objets.
Évidemment, c'est une relation: l' Team
objet aura des pointeurs vers son Members
. Et il est probable que les membres auront également un pointeur arrière sur leur Team
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éralement shared_ptr
et le «propriétaire» utilise a weak_ptr
pour son parent, et le convertit temporairement au shared_ptr
moment où il a besoin d'accéder à son parent.
Stocker un ptr faible:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
puis utilisez-le en cas de besoin
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
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 un shared_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 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'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.
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 de std::bind
captures lambda ou.
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";
}
} );
}
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é.
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 NULL shared_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.
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:
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());
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.
Outre les autres cas d'utilisation valides déjà mentionnés, std::weak_ptr
c'est un outil génial dans un environnement multithread, car
std::shared_ptr
en conjonction avec std::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.)
// a simplified class to hold the thumbnail and data
struct ImageData {
std::string path;
std::unique_ptr<YourFavoriteImageLibData> image;
};
// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
for( auto& imageData : imagesToLoad )
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas =
splitImageDatas( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
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'utiliser std::shared_ptr
en conjonction avec std::weak_ptr
.
// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
for( auto& imageDataWeak : imagesToLoad ) {
std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
if( !imageData )
continue;
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas =
splitImageDatasToWeak( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
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é.
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.
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.
weak_ptr
gérer une dépendance circulaire sans changement dans la logique du programme en remplacement du remplacement shared_ptr
?" :-)
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 a expiré.
Pour éviter une 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 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.
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;
}
production:
A()
B()
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.
Je vois std::weak_ptr<T>
comme une poignée à un std::shared_ptr<T>
: il me permet d'obtenir le std::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:
// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;
// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.
struct Widget {
std::weak_ptr< Texture > texture_handle;
void render() {
if (auto texture = texture_handle.get(); texture) {
// do stuff with texture. Warning: `texture`
// is now extending the lifetime because it
// is a std::shared_ptr< Texture >.
} else {
// gracefully degrade; there's no texture.
}
}
};
Un autre scénario important consiste à briser les cycles des structures de données.
// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > next;
std::shared_ptr< Node > prev;
};
// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::shared_ptr< Node > next;
std::weak_ptr< Node > prev;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
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.