2012-04-26 11 views
9

Considerate questo programma:shared_ptr multipla memorizzazione stesso puntatore

#include <memory> 
#include <iostream> 

class X 
    : public std::enable_shared_from_this<X> 
{ 
public: 
    struct Cleanup1 { void operator()(X*) const; }; 
    struct Cleanup2 { void operator()(X*) const; }; 
    std::shared_ptr<X> lock1(); 
    std::shared_ptr<X> lock2(); 
}; 

std::shared_ptr<X> X::lock1() 
{ 
    std::cout << "Resource 1 locked" << std::endl; 
    return std::shared_ptr<X>(this, Cleanup1()); 
} 

std::shared_ptr<X> X::lock2() 
{ 
    std::cout << "Resource 2 locked" << std::endl; 
    return std::shared_ptr<X>(this, Cleanup2()); 
} 

void X::Cleanup1::operator()(X*) const 
{ 
    std::cout << "Resource 1 unlocked" << std::endl; 
} 

void X::Cleanup2::operator()(X*) const 
{ 
    std::cout << "Resource 2 unlocked" << std::endl; 
} 

int main() 
{ 
    std::cout << std::boolalpha; 

    X x; 
    std::shared_ptr<X> p1 = x.lock1(); 
    { 
    std::shared_ptr<X> p2 = x.lock2(); 
    } 
} 

non vedo nulla in C++ 11 sezione standard 20.7.2 suggerendo niente di tutto questo non è valido. È un po 'strano che due oggetti shared_ptr memorizzino lo stesso puntatore &x ma non condividano la proprietà e usino "deleteri" che non terminano la durata di *get(), ma nulla lo vieta. (E se uno di questi sono del tutto non intenzionale, sarebbe difficile spiegare perché alcune shared_ptr funzioni membro accettano un valore di std::nullptr_t.) E come previsto, i risultati del programma:

Resource 1 locked 
Resource 2 locked 
Resource 2 unlocked 
Resource 1 unlocked 

Ma ora se posso aggiungere un po 'di main():

int main() 
{ 
    std::cout << std::boolalpha; 

    X x; 
    std::shared_ptr<X> p1 = x.lock1(); 
    bool test1(x.shared_from_this()); 
    std::cout << "x.shared_from_this() not empty: " << test1 << std::endl; 
    { 
    std::shared_ptr<X> p2 = x.lock2(); 
    } 
    try { 
    bool test2(x.shared_from_this()); 
    std::cout << "x.shared_from_this() not empty: " << test2 << std::endl; 
    } catch (std::exception& e) { 
    std::cout << "caught: " << e.what() << std::endl; 
    } 
} 

poi le cose si fanno più complicato. Con g ++ 4.6.3, ottengo l'output:

Resource 1 locked 
x.shared_from_this() not empty: true 
Resource 2 locked 
Resource 2 unlocked 
caught: std::bad_weak_ptr 
Resource 1 unlocked 

Perché la seconda chiamata a shared_from_this() fallire? sono soddisfatti tutti i requisiti di 20.7.2.4p7:

Richiede:enable_shared_from_this<T> sarà una classe base accessibile di T. *this deve essere un suboggetto di un oggetto t di tipo T. Ci deve essere almeno un ppproprietario&t.

[T è X, t è x, p è p1.]

Ma g ++ 's enable_shared_from_this segue essenzialmente l'attuazione suggerito dal (non-normativo) "Nota" in 20.7.2.4p10, utilizzando un privato weak_ptr membro della classe enable_shared_from_this. E sembra impossibile spiegare questo tipo di problema senza fare qualcosa di molto più complicato in enable_shared_from_this.

È un difetto nello standard? (In tal caso, qui non è necessario commentare su quale dovrebbe essere la soluzione: aggiungere un requisito in modo che il programma di esempio invochi il comportamento non definito, modificare la nota per non suggerire che una semplice implementazione sarebbe sufficiente, ....)

+1

Dal punto di vista degli standard, non ne sono sicuro. Il motivo PERCHÉ è che l'implementazione g ++ (e boost) si aspetta che la prima volta che crei un puntatore condiviso da una data istanza del puntatore raw di X sarà l'unica volta e la variabile privata weak_ptr è impostata per puntare a quell'istanza creata. Quando crei un secondo nuovo puntatore condiviso sulla stessa istanza in 'lock2()', sovrascrive l'originale weak_ptr, e quando si sblocca, il puntatore debole ora non punta a nulla, quindi l'errore. –

+0

La nota non normativa che dimostra un'implementazione di esempio di 'enable_from_this' conclude (al paragrafo 11) con" I costruttori shared_ptr che creano ** puntatori unici ** possono rilevare la presenza di una base 'enable_shared_from_this' e assegnare il nuovo creato' shared_ptr' al suo membro '__weak_this'." [enfasi mia] Trovo straordinario che questa nota non sia stata formulata con qualcosa per l'effetto di "i costruttori che creano * possedere * puntatori", e mi chiedo che cosa sia un unico 'shared_ptr'. –

+1

@Luc Credo che si riferisca ai costruttori che dopo la creazione restituirebbero true dal loro 'unique()'. In sostanza, i costruttori che prendono la proprietà iniziale da un puntatore raw o da unique_ptr. –

risposta

4

Sono d'accordo che questo è un buco nella specifica, quindi un difetto. È praticamente lo stesso di http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179 sebbene il problema derivi da un angolo leggermente diverso (e IMHO più ovviamente rotto).

Non sono sicuro di essere d'accordo sul fatto che si tratta di un uso improprio di shared_ptr, penso che sia corretto farlo con shared_ptrs, perché a differenza del codice nel problema 2179 si utilizzano i deletatori no-op. Penso che il problema sia quando si tenta di combinare questo tipo di utilizzo di shared_ptr con enable_shared_from_this.

Così il mio primo pensiero è stato quello di risolvere il problema estendendo i requisiti di shared_from_this:

Richiede:enable_shared_from_this<T> sarà una classe base accessibile di T. *this deve essere un suboggetto di un oggetto t di tipo T. Ci sarà almeno un shared_ptr esempio p che possiede&te qualsiasi altro shared_ptr istanze che possiedono &t devono condividere la proprietà con p.

Questo non è più che sufficiente, però, perché il vostro esempio soddisfa tale requisito: in seconda convocazione per shared_from_this() c'è un solo proprietario (p1), ma hai già "danneggiato" lo stato della classe di base enable_shared_from_this chiamando lo lock2().

Una forma più piccola del programma è:

#include <memory> 
using namespace std; 

int main() 
{ 
    struct X : public enable_shared_from_this<X> { }; 
    auto xraw = new X; 
    shared_ptr<X> xp1(xraw); // #1 
    { 
    shared_ptr<X> xp2(xraw, [](void*) { }); // #2 
    } 
    xraw->shared_from_this(); // #3 
} 

Tutti e tre libstdC++, libC++ e VC++ (Dinkumware) si comportano allo stesso e gettare bad_weak_ptr a # 3, perché a 2 # aggiornano il weak_ptr<X> membro del la classe base per renderla condivisa con lo xp2, che esce dallo scope lasciando lo weak_ptr<X> allo stato scaduto.

È interessante notare che boost::shared_ptr non genera, mentre il n. 2 è un no-op e il n. 3 restituisce uno shared_ptr che condivide la proprietà con xp1. Questo è stato fatto in risposta a un bug report con quasi esattamente lo stesso esempio di quello sopra.

3

Sì, c'è un difetto qui in C++ 11. Nel permettere questo:

E 'un po' insolito avere due oggetti shared_ptr memorizzano lo stesso puntatore & x, ma non condividere la proprietà, e di utilizzare "hanno eliminato" che non terminano il ciclo di vita * get(), ma niente lo proibisce.

questo dovrebbe essere esplicitamente dichiarati comportamento indefinito, a prescindere di ciò che il "hanno eliminato" fanno. Certo, potrebbe essere tecnicamente non illegale fare le cose in questo modo.

Tuttavia, si è che giace per le persone che utilizzano il codice.L'aspettativa di chi riceve uno shared_ptr è che ora hanno la proprietà dell'oggetto. Fintanto che mantengono quello shared_ptr (o una sua copia) in giro, l'oggetto a cui punta continuerà a esistere.

Questo non è il caso del tuo codice. Quindi direi che è sintatticamente corretto ma semanticamente non valido.

La lingua per shared_from_this va bene. È la lingua per shared_ptr che deve essere modificata. Dovrebbe indicare che è un comportamento indefinito creare due puntatori univoci separati che "possiedono" lo stesso puntatore.

+0

È' shared_ptr p1 (nullptr, d1); shared_ptr p2 (nullptr, d2); 'altrettanto invalido? – aschepler

+0

Cosa succede se fornisco la durata di archiviazione statica 'x' e rendo privati ​​il ​​ctor e il dtor di' X'? Quindi posso davvero garantire che se hai un 'shared_ptr non condiviso ', l'oggetto a cui punta esiste ancora (perché esiste sempre). – aschepler

+0

Non tentare di abbattere questo: penso che l'idea di base sarebbe una buona soluzione per lo standard, ma avrebbe bisogno di un testo più attento. – aschepler

Problemi correlati