2015-02-05 8 views
7
#include <memory> 
#include <iostream> 

struct A : public std::enable_shared_from_this<A> 
{ 
    ~A() 
    { 
     auto this_ptr = shared_from_this(); // std::bad_weak_ptr exception here. 
     std::cout << "this: " << this_ptr; 
    } 
}; 

int main() 
{ 
    auto a = std::make_shared<A>(); 
    a.reset(); 
    return 0; 
} 

Ricevo l'eccezione std::bad_weak_ptr quando si chiama shared_from_this(). È di progettazione? Sì, potrebbe essere pericoloso in quanto questo puntatore non può essere utilizzato dopo il ritorno del distruttore, ma non vedo una ragione per cui sarebbe tecnicamente impossibile ottenere il puntatore qui, poiché l'oggetto puntatore condiviso ovviamente esiste ancora e può essere Usato. C'è un modo per aggirare questo, a meno di scrivere il mio analogo enable_shared_from_this (che preferirei non fare)?std :: enable_shared_from_this: è consentito chiamare shared_from_this() in destructor?

+0

http://stackoverflow.com/q/8501503/1147772 – Drax

+0

@Drax: ho visto quella domanda. Riguarda 'boost' e non' std', e le risposte parlano del design specifico del codice in questione piuttosto che delle principali limitazioni sulla 'shared_from_this()' availability. –

+0

@VioletGiraffe La domanda non riguarda né 'boost' né' std', ma solo il concetto di riferimento debole. – curiousguy

risposta

6

Non vedo un motivo per cui sarebbe tecnicamente impossibile ottenere il puntatore qui, poiché l'oggetto puntatore condiviso ovviamente esiste ancora e può essere utilizzato.

C'è una ragione tecnica molto buona perché non è possibile.

Il shared_ptr potrebbe esistere, ma il conteggio dei riferimenti per l'oggetto A ha raggiunto lo zero, ecco perché viene eseguito il distruttore. Una volta che il conteggio dei riferimenti raggiunge lo zero, non può essere aumentato di nuovo (altrimenti potresti ottenere un shared_ptr che fa riferimento a un oggetto che sta eseguendo il suo distruttore o è già stato distrutto).

Chiamare shared_from_this() tenta di aumentare il numero di riferimenti e restituire un shared_ptr che condivide la proprietà con il proprietario corrente (s), ma non è possibile aumentare il contatore da zero a uno, quindi non riesce.

In questo caso molto specifico (all'interno distruttore dell'oggetto) si conosce l'oggetto non è stato ancora completamente distrutta, ma enable_shared_from_this<A> non ha modo di sapere chi sta chiamando la funzione shared_from_this(), quindi non si può sapere se si tratta di succede in questo caso molto specifico o in qualche altro pezzo di codice al di fuori del distruttore dell'oggetto (ad esempio in un altro thread che continuerà ad andare dopo il distruttore).

Se si potesse in qualche modo farlo funzionare per questo caso specifico e si ottenesse uno shared_ptr<A> che si riferiva all'oggetto attualmente distrutto, si poteva dare quel shared_ptr a qualcosa di esterno al distruttore che lo aveva memorizzato per un uso successivo. Ciò consentirebbe a quell'altra parte di codice di accedere a un penzoloni shared_ptr, dopo che l'oggetto è stato distrutto. Sarebbe un grosso buco nel sistema di tipi shared_ptr e weak_ptr.

+0

Ottima spiegazione, grazie. –

0

L'implementazione di shared_ptr::reset è spesso shared_ptr().swap(*this).

Ciò significa che lo shared_ptr che si sta tentando di copiare è già nel suo stato di distruttore che a sua volta decrementa il conteggio condiviso prima di chiamare il distruttore. Quando si chiama enable_shared_from_this si cercherà di promuovere la weak_ptr memorizzati al suo interno con la costruzione di un shared_ptr da quella weak_ptr che si traduce in un'eccezione quando il conteggio è 0.

Quindi, per rispondere alla tua domanda, non esiste un modo standard di fare ciò che vuoi se la tua implementazione della libreria standard non si comporta in modo tale da autorizzarla (non so se è richiesta dallo standard o meno).

Ora, ecco un trucco che funziona sulla mia macchina (clang/libC++):

#include <memory> 
#include <iostream> 

class hack_tag 
{ 
}; 

namespace std 
{ 

    template<> 
    class shared_ptr<hack_tag> 
    { 
    public: 
    template<typename T> 
    weak_ptr<T>  extract_weak(const enable_shared_from_this<T>& shared) 
    { 
     return shared.__weak_this_; 
    } 
    }; 

}; 

using weak_ptr_extractor = std::shared_ptr<hack_tag>; 

class test : public std::enable_shared_from_this<test> 
{ 
public: 
    test() 
    { 
    std::cout << "ctor" << std::endl; 
    } 

    ~test() 
    { 
    std::cout << "dtor" << std::endl; 
    weak_ptr_extractor hacker; 
    auto weak = hacker.extract_weak(*this); 
    std::cout << weak.use_count() << std::endl; 
    auto shared = weak.lock(); 
    } 
}; 


int  main(void) 
{ 
    std::shared_ptr<test> ptr = std::make_shared<test>(); 

    ptr.reset(); 
} 

ma non sono sicuro che si può fare qualcosa di utile con il che, poiché il vostro possedere shared_ptr che è stato copiato è di circa morire e quella copia non condivide le cose con il nuovo pulito shared_ptr si ottiene dopo la chiamata reset.

9

[util.smartptr.enab]/7 descrive i presupposti per shared_from_this:

Richiede:enable_shared_from_this<T> sarà una classe base accessibile T. *this deve essere un suboggetto di un oggetto t di tipo T. Ci deve essere almeno un'istanza di proprietà di &t. [emph. aggiunto]

Poiché l'oggetto viene distrutto, è necessario che non sia presente lo shared_ptr proprietario. Di conseguenza, non è possibile chiamare shared_from_this senza violare tale requisito risultante in un comportamento non definito.

+1

"Poiché il tuo oggetto viene distrutto, deve essere il caso che non ci sia' shared_ptr' che lo possiede. "A meno che qualcuno non sia abbastanza pazzo da fare una chiamata distruttiva esplicita;) –

+1

@ T.C. Se questo è il caso, allora "qualcuno" pensa ovviamente che * loro * sono in realtà il proprietario, e non il 'shared_ptr' che si riferisce all'oggetto. Il mio argomento resiste;) – Casey

+0

Pensavo che dal momento che 'shared_ptr' è responsabile della distruzione di un oggetto, il puntatore non verrà distrutto finché _after_ l'oggetto in esso contenuto viene eliminato. Ma a quanto pare, l'oggetto contatore di riferimento viene distrutto prima, almeno nell'implementazione di MS, quindi è già non disponibile nel distruttore. –

Problemi correlati