std::shared_ptr<X>
ha già un mucchio di spese generali su un grezzo B*
.
A shared_ptr<X>
fondamentalmente mantiene 4 cose. Mantiene un puntatore a B
, mantiene due conteggi di riferimento (un conteggio di riferimento "duro" e uno "soft" per weak_ptr
) e mantiene una funzione di pulizia.
La funzione di pulizia è il motivo per cui shared_ptr<X>
si comporta in modo diverso. Quando si crea un shared_ptr<X>
, una funzione che chiama il distruttore di quel particolare tipo viene creata e archiviata nella funzione di pulizia gestita dallo shared_ptr<X>
.
Quando si modificano i tipi gestiti (B*
diventa C*
), la funzione di pulizia rimane invariata.
Poiché shared_ptr<X>
deve gestire i conteggi di riferimento, il sovraccarico aggiuntivo di tale spazio di archiviazione di pulizia è marginale.
Per un unique_ptr<B>
, la classe è quasi economica come un grezzo B*
. Mantiene lo stato zero diverso dal suo B*
e il suo comportamento (alla distruzione) si riduce a if (b) delete b;
. (Sì, che if (b)
è ridondante, ma un ottimizzatore può capirlo).
Al fine di sostenere ghisa-to-base e delete-as-derivati, lo stato in più avrebbe dovuto essere memorizzato che ricorda il unique_ptr
è davvero a una classe derivata. Questo potrebbe essere sotto forma di un puntatore memorizzato a deleter, come un shared_ptr
.
Questo, tuttavia, raddoppierà le dimensioni di un unique_ptr<B>
o richiederà di memorizzare i dati nell'heap da qualche parte.
È stato deciso che unique_ptr<B>
dovrebbe essere zero-overhead e, pertanto, non supporta cast-to-base mentre sta ancora chiamando il distruttore della base.
Ora è possibile insegnare a unique_ptr<B>
semplicemente aggiungendo un tipo deleter e archiviando una funzione di distruzione che conosce il tipo di cosa che sta distruggendo. Quanto sopra ha parlato del deleter predefinito di unique_ptr
, che è stateless e banale.
struct deleter {
void* state;
void(*f)(void*);
void operator()(void*)const{if (f) f(state);}
deleter(deleter const&)=default;
deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
deleter()=delete;
template<class T>
deleter(T*t):
state(t),
f([](void*p){delete static_cast<T*>(p);})
{}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;
template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique(Args&&... args) {
T* t = new T(std::forward<Args>(args)...);
return { t, t };
}
live example, dove ho generare un unico-PTR TO derivata, conservarlo in un unico-PTR alla base, e quindi reimpostare base. Il puntatore derivato è cancellato.
(Un semplice void(*)(void*)
deleter potrebbe incorrere in problemi per cui il passato in void*
differiscano valore tra la base e casi derivati.)
noti che cambiando il puntatore memorizzato in tale unique_ptr
senza modificare il deleter risulterà nel comportamento mal consigliato.
Questo potrebbe aiutare: http://stackoverflow.com/questions/6876751/differences-between-unique-ptr-and-shared-ptr –