Risposte:
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.
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_ptr
gestione dei dati e fornendo std::weak_ptr
agli utenti dei dati, gli utenti possono verificare la validità dei dati chiamando expired()
o lock()
.
Non è possibile farlo da std::shared_ptr
soli, poiché tutte le std::shared_ptr
istanze condividono la proprietà dei dati che non vengono rimossi prima che std::shared_ptr
vengano 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";
}
std::weak_ptr::lock
crea un nuovo std::shared_ptr
che condivide la proprietà dell'oggetto gestito.
Un'altra risposta, si spera più semplice. (per compagni googler)
Supponi di avere Team
e Member
oggetti.
Ovviamente è una relazione: l' Team
oggetto avrà puntatori alla sua Members
. Ed è probabile che anche i membri dispongano di un puntatore posteriore al loro Team
oggetto.
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_ptr
e il "posseduto" usa a weak_ptr
per il suo genitore e lo converte temporaneamente in shared_ptr
quando 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
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_ptr
per cominciare.
shared_ptr
citata dai suoi "membri della squadra", quando verrà distrutta? Quello che stai descrivendo è un caso in cui non esiste un ciclo.
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.
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_ptr
l'oggetto gestore asincrono, usando std::bind
o 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.
this
self = shared_from_this()
linguaggio quando il gestore invoca metodi all'interno della stessa classe.
shared_ptr : contiene l'oggetto reale.
weak_ptr : usa lock
per connettersi al vero proprietario o restituisce un NULL in caso shared_ptr
contrario.
In parole povere, il weak_ptr
ruolo è 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.
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());
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:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
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:
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.
A parte gli altri casi d'uso già menzionati, std::weak_ptr
è uno strumento fantastico in un ambiente multithread, perché
std::shared_ptr
in combinazione con std::weak_ptr
è sicuro contro puntatori penzolanti - in opposizione a std::unique_ptr
in congiunzione con puntatori grezzistd::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_imageDatas
campo. 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 imagesToLoad
sarebbe 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_ptr
in 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.
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.
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.
weak_ptr
affrontare una dipendenza circolare senza cambiare la logica del programma in sostituzione del drop-in shared_ptr
?" :-)
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.
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.