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 diT
.*this
deve essere un suboggetto di un oggettot
di tipoT
. Ci deve essere almeno unp
p
proprietario&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, ....)
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. –
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'. –
@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. –