Quand std :: faiblesse_ptr est-il utile?

Réponses:

231

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.

David Schwartz
la source
8
Donc std :: wake_ptr ne peut pointer que là où un autre pointeur pointe et il pointe vers nullptr lorsque l'objet pointé est supprimé / non pointé par d'autres pointeurs?
27
@RM: Fondamentalement, oui. Lorsque vous avez un pointeur faible, vous pouvez essayer de le promouvoir en pointeur fort. Si cet objet existe toujours (car au moins un pointeur fort existe toujours), cette opération réussit et vous donne un pointeur fort vers lui. Si cet objet n'existe pas (car tous les pointeurs forts ont disparu), cette opération échoue (et vous réagissez généralement en jetant le pointeur faible).
David Schwartz
12
Alors qu'un pointeur fort maintient un objet en vie, un faible_ptr peut le regarder ... sans bouger avec la durée de vie de l'objet.
The Vivandiere
3
Un autre exemple, que j'ai utilisé plusieurs fois au moins, est lors de la mise en œuvre d'observateurs, il devient parfois pratique de laisser le sujet tenir une liste de pointeurs faibles et faire son propre nettoyage de liste. Cela économise un peu d'effort en supprimant explicitement les observateurs lorsqu'ils sont supprimés, et plus important encore, vous n'avez pas besoin d'informations sur les sujets disponibles lors de la destruction des observateurs, ce qui simplifie généralement beaucoup les choses.
Jason C
3
Attendez, qu'est-ce qui ne va pas avec le cache contenant un shared_ptr et le supprimant simplement de sa liste alors qu'il devrait être effacé de la mémoire? Tous les utilisateurs détiendront un shared_ptr tout de même et la ressource mise en cache sera effacée dès que tous les utilisateurs en auront fini.
rubenvb
299

std::weak_ptrest 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_ptrgérer les données et en fournissant std::weak_ptraux 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_ptrseul, car toutes les std::shared_ptrinstances partagent la propriété des données qui ne sont pas supprimées avant que toutes les instances de std::shared_ptrsoient 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";
}
sunefred
la source
1
Ok, c'est comme si vous définissiez localement un pointeur (propriétaire) sur null (supprimer la mémoire), tous les autres pointeurs (faibles) vers la même mémoire sont également définis sur null
Pat-Laugh
std::weak_ptr::lockcrée un nouveau std::shared_ptrpartage de la propriété de l'objet géré.
Sahib Yar
129

Une autre réponse, espérons-le plus simple. (pour les autres googleurs)

Supposons que vous avez Teamet Memberobjets.

Évidemment, c'est une relation: l' Teamobjet aura des pointeurs vers son Members. Et il est probable que les membres auront également un pointeur arrière sur leur Teamobjet.

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_ptret le «propriétaire» utilise a weak_ptrpour son parent, et le convertit temporairement au shared_ptrmoment 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
Offirmo
la source
1
Comment est-ce une fuite de mémoire? Si l'équipe est détruite, elle détruira ses membres, donc le nombre de références shared_ptr sera 0 et également détruit?
paulm
4
@paulm Team ne détruira pas «ses» membres. L'intérêt shared_ptrest 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_ptrpour commencer.
Offirmo
1
Il ne les détruira pas mais son shared_ptr sortira de son champ d'application, décrémentera le use_count, donc à ce stade, use_count est 0 et donc le shared_ptr supprimera ce vers quoi il pointe?
paulm
2
@paulm Vous avez raison. Mais puisque, dans cet exemple, l'équipe est également shared_ptrré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.
Offirmo
14
Ce n'est pas si mal, je pense. Si un membre peut appartenir à plusieurs équipes, l'utilisation d'une référence ne fonctionnera pas.
Mazyod
22

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.

Kerrek SB
la source
4
: Cela ressemble à un bon exemple, mais pouvez-vous développer votre exemple un peu plus? Je pense qu'une fois la tâche terminée, elle devrait déjà être supprimée du std :: vector <std :: faiblesse_ptr <Tâche>> sans vérification périodique. Donc, je ne sais pas si le std :: vector <std :: faiblesse_ptr <>> est très utile ici.
Gob00st
Commentaire similaire avec les files d'attente: disons que vous avez des objets et que vous les mettez en file d'attente pour une ressource, les objets peuvent être supprimés en attendant. Donc, si vous mettez en file d'attente faibles_ptrs, vous n'avez pas à vous soucier de supprimer des entrées de cette file d'attente. Weak_ptrs sera invalidé puis rejeté lors de la mise en place.
zzz777
1
@ zzz777: La logique qui invalide les objets peut même ne pas être au courant de l'existence de la file d'attente ou du vecteur d'observateurs. L'observateur effectue donc une boucle distincte sur les pointeurs faibles, agissant sur ceux qui sont encore en vie et retirant les morts du conteneur ...
Kerrek SB
1
@KerekSB: oui et en cas de file d'attente, vous n'avez même pas à une boucle distincte - alors la ressource est disponible vous jetez les faibles_ptrs expirés (le cas échéant) jusqu'à ce que vous en obteniez une valide (le cas échéant).
zzz777
Vous pouvez également demander aux threads de se retirer de la collection, mais cela créerait une dépendance et nécessiterait un verrouillage.
curiousguy
16

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_ptrdans l'objet gestionnaire asynchrone, à l'aide de std::bindcaptures 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é.

Emile Cormier
la source
Pourquoi at-il fallu si longtemps pour trouver cette réponse ... PS vous n'utilisez pas votre capture dethis
Orwellophile
@Orwellophile fixe. Force d'habitude lors de l'utilisation de l' self = shared_from_this()idiome lorsque le gestionnaire invoque des méthodes au sein de la même classe.
Emile Cormier
16

shared_ptr : contient l'objet réel.

faiblesse_ptr : utilise lockpour se connecter au vrai propriétaire ou renvoie un NULL shared_ptrsinon.

ptr faible

En gros, le weak_ptrrô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.

Saurav Sahu
la source
14

weak_ptrest é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());
Biscuit
la source
13

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:

  • Pointeurs bruts:
    • Pointeur brut [ie SomeClass* ptrToSomeClass = new SomeClass();]
  • Pointeurs intelligents:
    • Pointeurs uniques [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Pointeurs partagés [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Pointeurs faibles [ie
      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:

  • Vous êtes occupé et vos réunions 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 se poursuit après la fin de la réunion A, vous rejoindrez
  • Les deux scénarios suivants pourraient se jouer:
    • La réunion A se termine et la réunion B se poursuit, alors vous vous joignez
    • La réunion A se termine et la réunion B est également terminée, vous ne pouvez donc pas rejoindre

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.

Jeremy
la source
6

Outre les autres cas d'utilisation valides déjà mentionnés, std::weak_ptrc'est un outil génial dans un environnement multithread, car

  • Il ne possède pas l'objet et ne peut donc pas entraver la suppression dans un autre thread
  • std::shared_ptren conjonction avec std::weak_ptrest sûr contre les pointeurs pendants - contrairement à std::unique_ptren conjonction avec les pointeurs bruts
  • std::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_imageDataschamp. 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 imagesToLoadserait 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_ptren 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é.

user2328447
la source
2

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.

MYLOGOS
la source
" casser les références circulaires " comment?
curiousguy
2

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.

ashutosh
la source
Comment une référence faible peut-elle gérer une dépendance circulaire?
curiousguy
1
@curiousguy, un enfant utilise une référence faible au parent, puis le parent peut être désalloué lorsqu'il n'y a pas de références partagées (fortes) pointant vers lui. Ainsi, lors de l'accès au parent via l'enfant, la référence faible doit être testée pour voir si le parent est toujours disponible. Alternativement, pour éviter cette condition supplémentaire, un mécanisme de traçage de référence circulaire (balayage de marque ou sondage sur les décréments de décompte, qui ont tous deux de mauvaises performances asymptotiques) peut briser les références partagées circulaires lorsque les seules références partagées au parent et à l'enfant proviennent de chaque autre.
Shelby Moore III
@ShelbyMooreIII " doit tester pour voir si le parent est toujours disponible " oui, et vous devez pouvoir réagir correctement au cas indisponible! Ce qui ne se produit pas avec une référence réelle (c'est-à-dire forte). Ce qui signifie que la référence faible n'est pas une baisse de remplacement: elle nécessite un changement de logique.
curiousguy
2
@curiousguy, vous n'avez pas demandé "Comment peut-on weak_ptrgérer une dépendance circulaire sans changement dans la logique du programme en remplacement du remplacement shared_ptr?" :-)
Shelby Moore III
2

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.

Swapnil
la source
2

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.

Escualo
la source