Mi sono imbattuto in enable_shared_from_this
durante la lettura degli esempi Boost.Asio e dopo aver letto la documentazione sono ancora perso per come dovrebbe essere usato correttamente. Qualcuno può darmi un esempio e/o una spiegazione di quando usare questa lezione ha senso.Qual è l'utilità di `enable_shared_from_this`?
risposta
Consente di ottenere un'istanza valida shared_ptr
su this
, quando tutto ciò che si ha è this
. Senza di esso, non avresti modo di ottenere uno shared_ptr
a this
, a meno che tu ne abbia già uno come membro. Questo esempio dalla boost documentation for enable_shared_from_this:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
Il metodo f() restituisce una valida shared_ptr
, anche se non aveva alcuna istanza membro. Si noti che non si può semplicemente fare questo:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
Il puntatore condivisa che questa tornata avrà un conteggio di riferimento diverso da quello "corretto" uno, e uno di loro finirà per perdere e in possesso di un riferimento penzoloni quando l'oggetto è cancellato.
enable_shared_from_this
sta per essere una parte del nuovo standard C++ 0x, quindi è anche possibile ottenere da lì e da boost.
Un altro modo è aggiungere un membro weak_ptr<Y> m_stub
allo class Y
. Quindi scrivere:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Utile quando non è possibile modificare la classe che si sta derivanti da (ad esempio estendendo biblioteca di altre persone). Non dimenticare di inizializzare il membro, ad es. per m_stub = shared_ptr<Y>(this)
, è valido anche durante un costruttore.
È OK se ci sono più stub come questo nella gerarchia di ereditarietà, non impedirà la distruzione dell'oggetto.
Modifica: Come indicato correttamente dall'utente nobar, il codice distruggerebbe l'oggetto Y quando l'assegnazione è terminata e le variabili temporanee vengono distrutte. Pertanto la mia risposta è errata.
Se la tua intenzione è quella di produrre un 'shared_ptr <>' che non cancelli il suo pointee, questo è eccessivo.Puoi semplicemente dire 'return shared_ptr
Sembra improbabile che questa sia una soluzione funzionante. 'm_stub = shared_ptr
L'autore riconosce che questa risposta è sbagliata e probabilmente potrebbe semplicemente eliminarlo. Ma l'ultima volta che ha effettuato l'accesso in 4,5 anni non è probabile che lo faccia - qualcuno con poteri più alti potrebbe rimuovere questa falsa pista? –
dall'articolo Dr Dobbs su puntatori deboli, credo che questo esempio è più facile da capire (fonte: http://drdobbs.com/cpp/184402026):
... codice come questo non funziona correttamente:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Né dei due oggetti shared_ptr
conosce l'altro, quindi entrambi cercheranno di rilasciare la risorsa quando vengono distrutti. Questo di solito porta a problemi.
Allo stesso modo, se una funzione di membro ha bisogno di un oggetto shared_ptr
che possiede l'oggetto che viene chiamato in poi, è possibile non solo creare un oggetto al volo:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
Questo codice ha lo stesso problema come l'esempio precedente, sebbene in una forma più sottile. Quando viene creato, l'oggetto sp1
r possiede la risorsa appena assegnata. Il codice all'interno della funzione membro non conosce l'oggetto shared_ptr
, pertanto l'oggetto shared_ptr
restituito è diverso da sp1
.La copia del nuovo oggetto shared_ptr
su sp2
non aiuta; quando sp2
esce dal campo di applicazione, verrà rilasciata la risorsa e quando lo standard sp1
non sarà più disponibile, rilascerà nuovamente la risorsa.
Il modo per evitare questo problema è utilizzare il modello di classe enable_shared_from_this
. Il modello prende un argomento di tipo template, che è il nome della classe che definisce la risorsa gestita. Questa classe deve, a sua volta, essere derivata pubblicamente dal modello; in questo modo:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
Quando si esegue questa operazione, tenere a mente che l'oggetto su cui si chiama shared_from_this
deve essere di proprietà di un oggetto shared_ptr
. Questo non funzionerà:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
Grazie, questo illustra il problema che viene risolto meglio della risposta attualmente accettata. – goertzenator
+1: buona risposta. A parte, invece di 'shared_ptr sp1 (nuova S);' può essere preferibile usare 'shared_ptr sp1 = make_shared ();', vedi ad esempio http://stackoverflow.com/questions/18301511/stdshared -ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo –
Arun
Sono abbastanza sicuro che l'ultima riga dovrebbe leggere 'shared_ptr sp2 = p-> not_dangerous();' perché il trabocchetto qui è che devi ** crea un shared_ptr nel modo normale prima di chiamare 'shared_from_this()' la prima volta! ** È davvero facile sbagliare! Prima di C++ 17 è ** UB ** chiamare 'shared_from_this() 'prima esattamente uno shared_ptr è stato creato nel modo normale:' auto sptr = std :: make_shared (); 'o' shared_ptr sptr (nuovo S()); '. Fortunatamente da C++ 17 in poi lo farà. –
AnorZaken
Si noti che l'utilizzo di boost :: intrusive_ptr non soffre di questo problema. Questo è spesso un modo più comodo per aggirare questo problema.
Sì, ma 'enable_shared_from_this' consente di lavorare con un'API che accetta specificamente' shared_ptr <> '. A mio parere, una tale API di solito è * Doing Wrong * (poiché è meglio lasciare che qualcosa di più in alto nella pila possieda la memoria) ma se sei costretto a lavorare con una tale API, questa è una buona opzione. – cdunn2001
Ecco la mia spiegazione, dal punto di vista dei dadi e bulloni (la risposta più alta non ha fatto clic su di me). * Si noti che questo è il risultato di indagare la fonte per shared_ptr e enable_shared_from_this che viene fornito con Visual Studio 2012. Forse altri compilatori implementano enable_shared_from_this diversamente ... *
enable_shared_from_this<T>
aggiunge un weak_ptr<T>
un'istanza privato a T
che detiene il 'uno conteggio riferimenti reali 'per l'istanza di T
.
Così, quando si crea un shared_ptr<T>
su un nuovo T *, che weak_ptr interna T * s 'viene inizializzato con un refcount di 1. Il nuovo shared_ptr
si capiscono in fondo su questa weak_ptr
.
T
possono poi, nei suoi metodi, chiamare shared_from_this
per ottenere un'istanza di shared_ptr<T>
che addossato lo stesso numero di riferimento memorizzato internamente. In questo modo, hai sempre un posto in cui il conteggio di rif. Di T*
viene memorizzato invece di avere più istanze di shared_ptr
che non si conoscono l'un l'altro, e ognuna pensa di essere lo shared_ptr
che è responsabile del conteggio di rif. T
e dell'eliminazione quando il loro conteggio di riferimento raggiunge lo zero.
Questo è corretto, e la parte veramente importante è "Così, quando crei per la prima volta ..." perché questo è un ** requisito ** (come dici tu il weak_ptr non è inizializzato finché non passi il puntatore degli oggetti in un shared_ptr ctor!) e questo requisito è dove le cose possono andare terribilmente male se non stai attento. Se non crei shared_ptr prima di chiamare 'shared_from_this' ottieni UB - allo stesso modo se crei più di un shared_ptr ricevi anche UB. Devi in qualche modo assicurarti di creare una shared_ptr _exactly_ una volta. – AnorZaken
In altre parole, l'idea di 'enable_shared_from_this' è fragile sin da quando il punto è essere in grado di ottenere un' shared_ptr
"_internal weak_ptr viene inizializzato con un conteggio di 1_" ptr debole a T non è proprietario di ptr intelligente a T. Un ptr debole è un riferimento intelligente proprietario a informazioni sufficienti per creare un ptr proprietario che è una "copia" di un altro proprietario ptr . Un ptr debole non ha alcun riferimento. Ha accesso a un conteggio ref, come tutti i ref personali. – curiousguy
È esattamente lo stesso in C++ 11 e versioni successive: consente di abilitare la possibilità di restituire this
come puntatore condiviso dal momento che this
fornisce un puntatore non elaborato.
in altre parole, che consenta di attivare il codice come questo
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
in questo:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
Funzionerà solo se questi oggetti sono sempre gestiti da un 'shared_ptr'. Potresti voler cambiare l'interfaccia per assicurarti che sia il caso. – curiousguy
Sei assolutamente corretto @curiousguy. Questo è ovvio.Mi piace anche digitare tutto il mio shared_ptr per migliorare la leggibilità quando definisco le mie API pubbliche. Ad esempio, invece di 'std :: shared_ptr
+1. Il punto chiave è che la tecnica "ovvia" di restituire solo shared_ptr (this) è interrotta, perché questo genera la creazione di più oggetti shared_ptr distinti con conteggi di riferimento separati. Per questo motivo non devi mai creare più di una shared_ptr ** dallo stesso puntatore raw **. –
Si noti che in _C++ 11 e successive_, è ** perfettamente valido ** utilizzare un costruttore 'std :: shared_ptr' su un puntatore _raw ** ** se ** eredita da' std :: enable_shared_from_this' . ** Non so se ** la semantica di Boost sia stata aggiornata per supportare questo. –