Risposte:
Ti consente di ottenere shared_ptr
un'istanza valida per this
, quando tutto ciò che hai è this
. Senza di essa, si avrebbe alcun modo di ottenere un shared_ptr
a 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_ptr
costruttore 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_ptr
per un oggetto che è già gestito da un altro std::shared_ptr
non 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_ptr
oggetti 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_ptr
oggetto 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_pt
oggetto r sp1
possiede la risorsa appena allocata. Il codice all'interno della funzione membro S::dangerous
non conosce shared_ptr
quell'oggetto, quindi l' shared_ptr
oggetto da esso restituito è distinto sp1
. Copiare il nuovo shared_ptr
oggetto sp2
non aiuta; quando sp2
esce dall'ambito, rilascia la risorsa e quando sp1
esce 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_this
deve essere di proprietà di un shared_ptr
oggetto. 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 T
cui è 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_ptr
fondamentalmente torna su questo weak_ptr
.
T
può quindi, nei suoi metodi, chiamare shared_from_this
per 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_ptr
istanze che non si conoscono a vicenda, e ognuna pensa di essere il shared_ptr
responsabile del conteggio dei ref T
e 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_this
ottieni 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* t
in 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_this
ti 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 this
come puntatore condiviso poiché this
ti 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>::get
per 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_stub
membro 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_deleter
un 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, this
verranno eliminati e tutti i riferimenti successivi saranno sospesi.
enable_shared_from_this
, mantiene un valore weak_ptr
di se stesso (popolato dal ctor), restituito come shared_ptr
quando si chiama shared_from_this
. In altre parole, stai duplicando ciò che enable_shared_from_this
già fornisce.