Risposte:
Ti consente di ottenere shared_ptrun'istanza valida per this, quando tutto ciò che hai è this. Senza di essa, si avrebbe alcun modo di ottenere un shared_ptra this, a meno che non hai già avuto uno come membro. Questo esempio dalla documentazione boost per 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 un valido shared_ptr, anche se non ha avuto un'istanza membro. Nota che non puoi semplicemente farlo:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
Il puntatore condiviso che questo restituito avrà un conteggio dei riferimenti diverso da quello "corretto" e uno di essi finirà per perdere e mantenere un riferimento penzolante quando l'oggetto viene eliminato.
enable_shared_from_thisè diventato parte dello standard C ++ 11. Puoi anche ottenerlo da lì e anche da boost.
std::shared_ptrcostruttore su un puntatore non elaborato se eredita da std::enable_shared_from_this. Non so se la semantica di Boost sia stata aggiornata per supportare questo.
std::shared_ptrper un oggetto che è già gestito da un altro std::shared_ptrnon consulti il riferimento debole memorizzato internamente e quindi porterà a un comportamento indefinito". ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p?
std::make_shared<T>.
dall'articolo del Dr Dobbs sui puntatori deboli, penso che questo esempio sia più facile da capire (fonte: http://drdobbs.com/cpp/184402026 ):
... codice come questo non funzionerà correttamente:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Nessuno dei due shared_ptroggetti conosce l'altro, quindi entrambi proveranno a rilasciare la risorsa quando vengono distrutti. Questo di solito porta a problemi.
Allo stesso modo, se una funzione membro ha bisogno di un shared_ptroggetto proprietario dell'oggetto su cui viene chiamato, non può semplicemente 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 presenta lo stesso problema dell'esempio precedente, sebbene in una forma più sottile. Quando viene costruito, l' shared_ptoggetto r sp1possiede la risorsa appena allocata. Il codice all'interno della funzione membro S::dangerousnon conosce shared_ptrquell'oggetto, quindi l' shared_ptroggetto da esso restituito è distinto sp1. Copiare il nuovo shared_ptroggetto sp2non aiuta; quando sp2esce dall'ambito, rilascia la risorsa e quando sp1esce dall'ambito, rilascerà nuovamente la risorsa.
Il modo per evitare questo problema è utilizzare il modello di classe enable_shared_from_this. Il modello accetta un argomento del tipo di modello, che è il nome della classe che definisce la risorsa gestita. A sua volta, tale classe deve essere derivata pubblicamente dal modello; come questo:
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 lo fai, tieni presente che l'oggetto su cui chiami shared_from_thisdeve essere di proprietà di un shared_ptroggetto. Questo non funzionerà:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);può essere preferito usarlo shared_ptr<S> sp1 = make_shared<S>();, vedi ad esempio stackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();perché la trappola qui è che devi creare un shared_ptr nel modo normale prima di chiamare shared_from_this()la prima volta! È davvero facile sbagliarsi! Prima di C ++ 17 è UB chiamare shared_from_this()prima che esattamente un shared_ptr sia stato creato nel modo normale: auto sptr = std::make_shared<S>();o shared_ptr<S> sptr(new S());. Per fortuna dal C ++ 17 in poi, lo farà.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();<- È consentito chiamare shared_from_this solo su un oggetto precedentemente condiviso, ovvero su un oggetto gestito da std :: shared_ptr <T>. Altrimenti il comportamento non è definito (fino a C ++ 17) viene lanciato std :: bad_weak_ptr (dal costruttore shared_ptr da un punto debole costruito di default) (dal C ++ 17). . Quindi la realtà è che dovrebbe essere chiamato always_dangerous(), perché è necessario sapere se è già stato condiviso o no.
Ecco la mia spiegazione, dal punto di vista generale (la risposta principale non mi ha "cliccato"). * Si noti che questo è il risultato dell'indagine dell'origine di shared_ptr e enable_shared_from_this fornita con Visual Studio 2012. Forse altri compilatori implementano enable_shared_from_this in modo diverso ... *
enable_shared_from_this<T>aggiunge weak_ptr<T>un'istanza privata a Tcui è associato il " conteggio dei veri riferimenti " per l'istanza di T.
Quindi, quando si crea per la prima volta un shared_ptr<T>su un nuovo T *, il suo debole_ptr interno di T * viene inizializzato con un refcount di 1. Il nuovo shared_ptrfondamentalmente torna su questo weak_ptr.
Tpuò quindi, nei suoi metodi, chiamare shared_from_thisper ottenere un'istanza di shared_ptr<T>tali backup sullo stesso conteggio dei riferimenti memorizzati internamente . In questo modo, hai sempre un posto in cui T*è memorizzato il conteggio dei riferimenti invece di avere più shared_ptristanze che non si conoscono a vicenda, e ognuna pensa di essere il shared_ptrresponsabile del conteggio dei ref Te dell'eliminazione quando il loro riferimento -count raggiunge lo zero.
So, when you first create...perché è un requisito (come dici tu, il punto debole non è inizializzato fino a quando non passi il puntatore degli oggetti in un ctor shared_ptr!) E questo requisito è dove le cose possono andare terribilmente sbagliate se lo sei non attento. Se non crei shared_ptr prima di chiamare shared_from_thisottieni UB - allo stesso modo se crei più di shared_ptr ottieni anche UB. Devi in qualche modo assicurarti di creare un shared_ptr esattamente una volta.
enable_shared_from_thisè fragile per cominciare dal momento che il punto è quello di essere in grado di ottenere un shared_ptr<T>da a T*, ma in realtà quando si ottiene un puntatore T* tin genere non è sicuro supporre che qualcosa sia già condiviso o no, e fare l'ipotesi sbagliata è UB.
Si noti che l'utilizzo di un boost :: intrusive_ptr non presenta questo problema. Questo è spesso un modo più conveniente per aggirare questo problema.
enable_shared_from_thisti consente di lavorare con un'API che accetta specificamente shared_ptr<>. A mio avviso, un'API di questo genere sta facendo Do It It Wrong (poiché è meglio lasciare che qualcosa di più alto nello stack possieda la memoria) ma se sei costretto a lavorare con tale API, questa è una buona opzione.
È esattamente lo stesso in c ++ 11 e versioni successive: è per abilitare la possibilità di tornare thiscome puntatore condiviso poiché thisti dà un puntatore non elaborato.
in altre parole, ti permette di trasformare il codice in questo modo
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;
};
shared_ptr. Potresti voler cambiare l'interfaccia per assicurarti che sia il caso.
std::shared_ptr<Node> getParent const(), normalmente lo espongo come NodePtr getParent const()invece. Se hai assolutamente bisogno dell'accesso al puntatore non elaborato interno (esempio migliore: occuparsi di una libreria C), ce n'è std::shared_ptr<T>::getper quello, che odio menzionare perché ho questo acceditore puntatore non elaborato usato troppe volte per la ragione sbagliata.
Un altro modo è quello di aggiungere un weak_ptr<Y> m_stubmembro in class Y. Allora scrivi:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Utile quando non è possibile modificare la classe da cui si deriva (ad es. Estendere la biblioteca di altre persone). Non dimenticare di inizializzare il membro, ad esempio m_stub = shared_ptr<Y>(this), è valido anche durante un costruttore.
Va bene se ci sono più tronchi come questo nella gerarchia ereditaria, non impedirà la distruzione dell'oggetto.
Modifica: Come sottolineato correttamente dall'utente nobar, il codice distruggerebbe l'oggetto Y al termine dell'assegnazione e le variabili temporanee verranno distrutte. Pertanto la mia risposta non è corretta.
shared_ptr<>che non elimini la sua punta, questo è eccessivo. Puoi semplicemente dire return shared_ptr<Y>(this, no_op_deleter);dov'è che no_op_deleterun oggetto funzione unario prende Y*e non fa nulla.
m_stub = shared_ptr<Y>(this)costruirà e distruggerà immediatamente un shared_ptr temporaneo da questo. Al termine di questa affermazione, thisverranno eliminati e tutti i riferimenti successivi saranno sospesi.
enable_shared_from_this, mantiene un valore weak_ptrdi se stesso (popolato dal ctor), restituito come shared_ptrquando si chiama shared_from_this. In altre parole, stai duplicando ciò che enable_shared_from_thisgià fornisce.