Quando è utile std :: weak_ptr?


Risposte:


231

Un buon esempio potrebbe essere una cache.

Per gli oggetti a cui hai avuto accesso di recente, vuoi tenerli in memoria, quindi tieni un puntatore forte su di essi. Periodicamente, si esegue la scansione della cache e si decide quali oggetti non sono stati aperti di recente. Non è necessario tenerli in memoria, quindi ti libererai del puntatore forte.

Ma cosa succede se quell'oggetto è in uso e qualche altro codice ha un forte puntatore ad esso? Se la cache elimina il suo unico puntatore all'oggetto, non la troverà mai più. Quindi la cache mantiene un puntatore debole sugli oggetti che deve trovare se rimangono nella memoria.

Questo è esattamente ciò che fa un puntatore debole: ti permette di localizzare un oggetto se è ancora in giro, ma non lo tiene in giro se nient'altro ne ha bisogno.


8
Quindi std :: wake_ptr può puntare solo dove punta un altro puntatore e punta a nullptr quando l'oggetto puntato viene eliminato / non più puntato da altri puntatori?

27
@RM: Fondamentalmente sì. Quando hai un puntatore debole, puoi provare a promuoverlo a un puntatore forte. Se quell'oggetto esiste ancora (perché esiste almeno un puntatore forte ad esso) quell'operazione ha successo e ti dà un puntatore forte ad esso. Se quell'oggetto non esiste (perché tutti i puntatori forti sono spariti), allora l'operazione fallisce (e in genere si reagisce gettando via il puntatore debole).
David Schwartz,

12
Mentre un puntatore forte mantiene in vita un oggetto, un debole_ptr può guardarlo ... senza perdere tempo con la vita dell'oggetto.
Il Vivandiere,

3
Un altro esempio, che ho usato almeno alcune volte, è quando si implementano gli osservatori, a volte diventa conveniente avere il soggetto a mantenere un elenco di puntatori deboli e fare la propria pulizia dell'elenco. Si risparmia un po 'di sforzo rimuovendo esplicitamente gli osservatori quando vengono eliminati, e più significativamente non è necessario avere informazioni sugli argomenti disponibili quando si distruggono gli osservatori che generalmente semplificano molto le cose.
Jason C,

3
Aspetta, cosa c'è che non va nella cache che contiene un shared_ptr e lo rimuove dal suo elenco quando dovrebbe essere cancellato dalla memoria? Tutti gli utenti manterranno un shared_ptr lo stesso e la risorsa memorizzata nella cache verrà cancellata non appena tutti gli utenti avranno terminato.
rubenvb,

299

std::weak_ptrè un ottimo modo per risolvere il problema del puntatore penzolante . Utilizzando solo puntatori non elaborati è impossibile sapere se i dati di riferimento sono stati deallocati o meno. Invece, consentendo una std::shared_ptrgestione dei dati e fornendo std::weak_ptragli utenti dei dati, gli utenti possono verificare la validità dei dati chiamando expired()o lock().

Non è possibile farlo da std::shared_ptrsoli, poiché tutte le std::shared_ptristanze condividono la proprietà dei dati che non vengono rimossi prima che std::shared_ptrvengano rimosse tutte le istanze . Ecco un esempio di come verificare la presenza di puntatori penzolanti usando 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";
}

1
Ok, è come se impostassi localmente un puntatore (proprietario) su null (elimina memoria), anche tutti gli altri puntatori (deboli) sulla stessa memoria sono impostati su null
Pat-Laugh,

std::weak_ptr::lockcrea un nuovo std::shared_ptrche condivide la proprietà dell'oggetto gestito.
Sahib Yar

129

Un'altra risposta, si spera più semplice. (per compagni googler)

Supponi di avere Teame Memberoggetti.

Ovviamente è una relazione: l' Teamoggetto avrà puntatori alla sua Members. Ed è probabile che anche i membri dispongano di un puntatore posteriore al loro Teamoggetto.

Quindi hai un ciclo di dipendenza. Se lo usi shared_ptr, gli oggetti non verranno più liberati automaticamente quando abbandonerai il riferimento su di essi, perché si riferiscono reciprocamente in modo ciclico. Questa è una perdita di memoria.

Si interrompe utilizzando weak_ptr. Il "proprietario" di solito usa shared_ptre il "posseduto" usa a weak_ptrper il suo genitore e lo converte temporaneamente in shared_ptrquando deve accedere al suo genitore.

Memorizza un punto debole:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

quindi usalo quando necessario

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

1
Come è una perdita di memoria? Se il team viene distrutto, distruggerà i suoi membri, quindi il conteggio dei riferimenti di shared_ptr sarà 0 e anche distrutto?
paulm

4
Il team di @paulm non distruggerà i "suoi" membri. Il punto shared_ptrè condividere la proprietà, quindi nessuno ha la responsabilità particolare di liberare la memoria, che viene liberata automaticamente quando non viene più utilizzata. A meno che non ci sia un ciclo ... Potresti avere più squadre che condividono lo stesso giocatore (squadre precedenti?). Se l'oggetto team "possiede" i membri, non è necessario utilizzare a shared_ptrper cominciare.
Offirmo,

1
Non li distruggerà ma il suo shared_ptr uscirà dal suo ambito, decrementa use_count, quindi a questo punto use_count è 0 e quindi shared_ptr eliminerà ciò a cui punta?
paulm

2
@paulm Hai ragione. Ma dal momento che, in questo esempio, la squadra viene anche shared_ptrcitata dai suoi "membri della squadra", quando verrà distrutta? Quello che stai descrivendo è un caso in cui non esiste un ciclo.
Offirmo,

14
Non è poi così male, penso. Se un membro può appartenere a molti team, l'utilizzo di un riferimento non funzionerà.
Mazyod,

22

Ecco un esempio, dato da @jleahy: Supponiamo di avere una raccolta di compiti, eseguita in modo asincrono e gestita da un std::shared_ptr<Task>. Potresti voler fare periodicamente qualcosa con tali compiti, quindi un evento timer può attraversare un std::vector<std::weak_ptr<Task>>e dare ai compiti qualcosa da fare. Tuttavia, contemporaneamente un'attività potrebbe aver deciso contemporaneamente che non è più necessario e morire. Il timer può quindi verificare se l'attività è ancora attiva creando un puntatore condiviso dal puntatore debole e utilizzando quel puntatore condiviso, purché non sia nullo.


4
: Sembra un buon esempio, ma puoi per favore elaborare un altro esempio? Sto pensando che quando un'attività è finita, dovrebbe essere già stata rimossa da std :: vector <std :: weak_ptr <Task>> senza un controllo periodico. Quindi non sono sicuro che std :: vector <std :: weak_ptr <>> sia molto utile qui.
Gob00st

Commento simile con le code: dire che hai oggetti e li metti in coda per alcune risorse, gli oggetti potrebbero essere eliminati durante l'attesa. Quindi, se fai la coda di deboli_ptrs non devi preoccuparti di cancellare le voci da quella coda. Weak_ptrs verrà invalidato e quindi scartato quando incontrato.
zzz777,

1
@ zzz777: la logica che invalida gli oggetti potrebbe non essere nemmeno a conoscenza dell'esistenza della coda o del vettore degli osservatori. Quindi l'osservatore esegue un ciclo separato sui punti deboli, agendo su quelli che sono ancora vivi e rimuovendo i morti dal contenitore ...
Kerrek SB

1
@KerekSB: sì e in caso di coda non è nemmeno necessario un loop separato - quindi la risorsa è disponibile si scartano weak_ptrs scaduti (se presenti) fino a quando non si ottiene uno valido (se presente).
zzz777,

Potresti anche rimuovere i thread dalla raccolta, ma ciò creerebbe una dipendenza e richiederebbe il blocco.
curioso

16

Sono utili con Boost.Asio quando non si è certi che esista ancora un oggetto target quando viene invocato un gestore asincrono. Il trucco è legare a weak_ptrl'oggetto gestore asincrono, usando std::bindo acquisizioni 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";
        }
    } );
}

Questa è una variante del self = shared_from_this()linguaggio spesso visto in Boost. Esempi di Asio, in cui un gestore asincrono in sospeso non prolungherà la durata dell'oggetto target, ma è comunque sicuro se l'oggetto target viene eliminato.


Perché ci è voluto così tanto tempo per trovare questa risposta ... PS non stai usando la tua catturathis
Orwellophile

@Orwellophile fixed. Forza dell'abitudine quando si usa il self = shared_from_this()linguaggio quando il gestore invoca metodi all'interno della stessa classe.
Emile Cormier,

16

shared_ptr : contiene l'oggetto reale.

weak_ptr : usa lockper connettersi al vero proprietario o restituisce un NULL in caso shared_ptrcontrario.

punto debole

In parole povere, il weak_ptrruolo è simile al ruolo dell'agenzia immobiliare . Senza agenti, per ottenere una casa in affitto potremmo dover controllare case a caso in città. Gli agenti si assicurano che visitiamo solo quelle case che sono ancora accessibili e disponibili per l'affitto.


14

weak_ptrè anche utile verificare la corretta eliminazione di un oggetto, specialmente nei test unitari. Il tipico caso d'uso potrebbe apparire così:

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());

13

Quando si utilizzano i puntatori è importante comprendere i diversi tipi di puntatori disponibili e quando ha senso usarli. Esistono quattro tipi di puntatori in due categorie come segue:

  • Puntatori non elaborati:
    • Raw Pointer [ie SomeClass* ptrToSomeClass = new SomeClass();]
  • Puntatori intelligenti:
    • Puntatori unici [ie
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Puntatori condivisi [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Puntatori deboli [ie
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

I puntatori non elaborati (a volte denominati "puntatori legacy" o "puntatori C") forniscono un comportamento dei puntatori "bare-bones" e sono una fonte comune di bug e perdite di memoria. I puntatori non elaborati non forniscono alcun mezzo per tenere traccia della proprietà della risorsa e gli sviluppatori devono chiamare manualmente "Elimina" per assicurarsi che non creino una perdita di memoria. Questo diventa difficile se la risorsa è condivisa in quanto può essere difficile sapere se qualche oggetto punta ancora alla risorsa. Per questi motivi, i puntatori non elaborati dovrebbero generalmente essere evitati e utilizzati solo in sezioni del codice critiche per le prestazioni con ambito limitato.

I puntatori univoci sono un puntatore intelligente di base che "possiede" il puntatore non elaborato sottostante alla risorsa ed è responsabile della chiamata a eliminare e liberare la memoria allocata una volta che l'oggetto che "possiede" il puntatore univoco non rientra nell'ambito. Il nome "unico" si riferisce al fatto che solo un oggetto può "possedere" il puntatore univoco in un determinato momento. La proprietà può essere trasferita su un altro oggetto tramite il comando sposta, ma un puntatore univoco non può mai essere copiato o condiviso. Per questi motivi, i puntatori univoci sono una buona alternativa ai puntatori non elaborati nel caso in cui solo un oggetto abbia bisogno del puntatore in un determinato momento, e questo allevia lo sviluppatore dalla necessità di liberare memoria al termine del ciclo di vita dell'oggetto proprietario.

I puntatori condivisi sono un altro tipo di puntatore intelligente che sono simili a puntatori univoci, ma consentono a molti oggetti di avere la proprietà sul puntatore condiviso. Come un puntatore univoco, i puntatori condivisi sono responsabili della liberazione della memoria allocata una volta che tutti gli oggetti hanno fatto puntamento alla risorsa. Ciò si ottiene con una tecnica chiamata conteggio dei riferimenti. Ogni volta che un nuovo oggetto diventa proprietario del puntatore condiviso, il conteggio dei riferimenti viene incrementato di uno. Allo stesso modo, quando un oggetto esce dall'ambito o smette di puntare alla risorsa, il conteggio dei riferimenti viene diminuito di uno. Quando il conteggio dei riferimenti raggiunge lo zero, la memoria allocata viene liberata. Per questi motivi, i puntatori condivisi sono un tipo molto potente di puntatore intelligente che dovrebbe essere utilizzato ogni volta che più oggetti devono puntare alla stessa risorsa.

Infine, i puntatori deboli sono un altro tipo di puntatore intelligente che, anziché puntare direttamente a una risorsa, puntano a un altro puntatore (debole o condiviso). I puntatori deboli non possono accedere direttamente a un oggetto, ma possono dire se l'oggetto esiste ancora o se è scaduto. Un puntatore debole può essere temporaneamente convertito in un puntatore condiviso per accedere all'oggetto puntato (purché esista ancora). Per illustrare, prendere in considerazione il seguente esempio:

  • Sei occupato e hai riunioni sovrapposte: Riunione A e Riunione B
  • Decidi di andare alla riunione A e il tuo collega va alla riunione B
  • Dici al tuo collega che se la Riunione B continua dopo la Riunione A, ti unirai
  • Potrebbero verificarsi i seguenti due scenari:
    • La riunione A termina e la riunione B continua, quindi ti unisci
    • La riunione A termina e anche la riunione B è terminata, quindi non puoi partecipare

Nell'esempio, hai un puntatore debole alla Riunione B. Non sei un "proprietario" nella Riunione B, quindi può terminare senza di te e non sai se è finita o meno a meno che non controlli. Se non è finito, puoi unirti e partecipare, altrimenti non puoi. Ciò è diverso dall'avere un puntatore condiviso alla Riunione B perché si sarebbe quindi un "proprietario" sia nella Riunione A che nella Riunione B (partecipando a entrambi contemporaneamente).

L'esempio mostra come funziona un puntatore debole ed è utile quando un oggetto deve essere un osservatore esterno , ma non vuole la responsabilità di condividere la proprietà. Ciò è particolarmente utile nello scenario in cui due oggetti devono puntare l'uno verso l'altro (ovvero un riferimento circolare). Con i puntatori condivisi, nessuno degli oggetti può essere rilasciato perché sono ancora "fortemente" indicati dall'altro oggetto. Quando uno dei puntatori è un puntatore debole, l'oggetto che contiene il puntatore debole può comunque accedere all'altro oggetto quando necessario, purché esista ancora.


6

A parte gli altri casi d'uso già menzionati, std::weak_ptrè uno strumento fantastico in un ambiente multithread, perché

  • Non possiede l'oggetto e quindi non può ostacolare la cancellazione in un thread diverso
  • std::shared_ptrin combinazione con std::weak_ptrè sicuro contro puntatori penzolanti - in opposizione a std::unique_ptrin congiunzione con puntatori grezzi
  • std::weak_ptr::lock()è un'operazione atomica (vedi anche Informazioni sulla sicurezza dei thread di weak_ptr )

Considerare un'attività per caricare contemporaneamente tutte le immagini di una directory (~ 10.000) nella memoria (ad es. Come cache delle miniature). Ovviamente il modo migliore per farlo è un thread di controllo, che gestisce e gestisce le immagini e più thread di lavoro, che caricano le immagini. Questo è un compito facile. Ecco un'implementazione molto semplificata ( join()ecc. Viene omesso, i thread dovrebbero essere gestiti in modo diverso in un'implementazione reale ecc.)

// 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) );
   }
};

Ma diventa molto più complicato, se si desidera interrompere il caricamento delle immagini, ad esempio perché l'utente ha scelto una directory diversa. O anche se vuoi distruggere il manager.

Avresti bisogno della comunicazione tra i thread e dovrai interrompere tutti i thread del caricatore, prima di poter cambiare m_imageDatascampo. Altrimenti i caricatori continuerebbero a caricare fino al completamento di tutte le immagini, anche se sono già obsolete. Nell'esempio semplificato, ciò non sarebbe troppo difficile, ma in un ambiente reale le cose possono essere molto più complicate.

I thread probabilmente faranno parte di un pool di thread utilizzato da più gestori, di cui alcuni vengono arrestati e alcuni non lo sono, ecc. Il parametro semplice imagesToLoadsarebbe una coda bloccata, in cui tali gestori inviano le loro richieste di immagine da diversi thread di controllo con i lettori che fanno scattare le richieste - in un ordine arbitrario - dall'altra parte. E così la comunicazione diventa difficile, lenta e soggetta a errori. Un modo molto elegante per evitare qualsiasi comunicazione aggiuntiva in tali casi è l'uso std::shared_ptrin combinazione con 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) );
   }
};

Questa implementazione è quasi facile come la prima, non ha bisogno di alcuna comunicazione aggiuntiva sul thread e potrebbe far parte di un pool / coda di thread in una vera implementazione. Poiché le immagini scadute vengono ignorate e le immagini non scadute vengono elaborate, i thread non dovrebbero mai essere arrestati durante il normale funzionamento. Puoi sempre cambiare in sicurezza il percorso o distruggere i tuoi manager, dal momento che il lettore controlla se il puntatore proprietario non è scaduto.


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr è un puntatore intelligente che contiene un riferimento non proprietario ("debole") a un oggetto gestito da std :: shared_ptr. Deve essere convertito in std :: shared_ptr per accedere all'oggetto referenziato.

std :: weak_ptr modella la proprietà temporanea: quando è necessario accedere a un oggetto solo se esiste e può essere eliminato in qualsiasi momento da qualcun altro, std :: weak_ptr viene utilizzato per tracciare l'oggetto e viene convertito in std: : shared_ptr per assumere la proprietà temporanea. Se lo std :: shared_ptr originale viene distrutto in questo momento, la durata dell'oggetto viene estesa fino a quando viene distrutto anche lo std :: shared_ptr temporaneo.

Inoltre, std :: weak_ptr viene utilizzato per rompere i riferimenti circolari di std :: shared_ptr.


" rompere i riferimenti circolari " come?
curiousguy,

2

C'è un inconveniente del puntatore condiviso: shared_pointer non può gestire la dipendenza del ciclo genitore-figlio. Indica se la classe genitore utilizza l'oggetto della classe figlio utilizzando un puntatore condiviso, nello stesso file se la classe figlio utilizza l'oggetto della classe genitore. Il puntatore condiviso non riuscirà a distruggere tutti gli oggetti, anche il puntatore condiviso non chiama affatto il distruttore nello scenario di dipendenza del ciclo. il puntatore sostanzialmente condiviso non supporta il meccanismo di conteggio dei riferimenti.

Questo svantaggio possiamo superare usando il punto debole.


Come può un riferimento debole gestire una dipendenza circolare?
curiousguy,

1
@curiousguy, un bambino impiega un riferimento debole al genitore, quindi il genitore può essere deallocato quando non ci sono riferimenti condivisi (forti) che lo puntano. Pertanto, quando si accede al genitore tramite il figlio, il riferimento debole deve essere testato per vedere se il genitore è ancora disponibile. In alternativa, per evitare quella condizione aggiuntiva, un meccanismo di tracciamento dei riferimenti circolari (mark-sweep o sondaggio su decrementi di contatori, entrambi con cattive prestazioni asintotiche) può interrompere i riferimenti circolari condivisi quando i soli riferimenti condivisi a padre e figlio provengono da ciascuno altro.
Shelby Moore III,

@ShelbyMooreIII " deve verificare se il genitore è ancora disponibile " sì, e devi essere in grado di reagire correttamente al caso non disponibile! Che non si verifica con un riferimento reale (cioè forte). Ciò significa che il riferimento debole non è un calo nella sostituzione: richiede un cambiamento nella logica.
curioso

2
@curiousguy non hai chiesto "Come si può weak_ptraffrontare una dipendenza circolare senza cambiare la logica del programma in sostituzione del drop-in shared_ptr?" :-)
Shelby Moore III,

2

Quando non vogliamo possedere l'oggetto:

Ex:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

Nella classe precedente wPtr1 non possiede la risorsa indicata da wPtr1. Se la risorsa viene eliminata, wPtr1 è scaduto.

Per evitare la dipendenza circolare:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Ora, se realizziamo shared_ptr della classe B e A, use_count di entrambi i puntatori è due.

Quando shared_ptr esce dall'ambito, il conteggio rimane ancora 1 e quindi l'oggetto A e B non viene eliminato.

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;  
}

produzione:

A()
B()

Come possiamo vedere dall'output che i puntatori A e B non vengono mai eliminati e quindi perdita di memoria.

Per evitare tale problema basta usare weak_ptr in classe A invece di shared_ptr che ha più senso.


2

Vedo std::weak_ptr<T>un handle per un std::shared_ptr<T>: mi permette di ottenere il std::shared_ptr<T>se esiste ancora, ma non prolungherà la sua vita. Esistono diversi scenari in cui tale punto di vista è 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 altro scenario importante è interrompere i cicli nelle strutture di dati.

// 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 ha un discorso eccellente che spiega il miglior uso delle funzionalità del linguaggio (in questo caso puntatori intelligenti) per garantire la perdita di perdita per impostazione predefinita (significato: tutto scatta in posizione per costruzione; difficilmente puoi rovinarlo). È un must da guardare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.