La domanda rientra davvero nel titolo: sono curioso di sapere qual è la ragione tecnica di questa differenza, ma anche la logica?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
La domanda rientra davvero nel titolo: sono curioso di sapere qual è la ragione tecnica di questa differenza, ma anche la logica?
std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;
Risposte:
È perché std::shared_ptr
implementa la cancellazione del tipo, mentre std::unique_ptr
non lo fa.
Poiché std::shared_ptr
implementa la cancellazione del tipo, supporta anche un'altra proprietà interessante, vale a dire. non ha bisogno del tipo di deleter come argomento del tipo di modello per il modello di classe. Guarda le loro dichiarazioni:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
che ha Deleter
come parametro di tipo, while
template<class T>
class shared_ptr;
non ce l'ha.
La domanda è: perché shared_ptr
implementa la cancellazione del tipo? Bene, lo fa, perché deve supportare il conteggio dei riferimenti e, per supportarlo, deve allocare la memoria dall'heap e poiché deve allocare comunque la memoria, fa un ulteriore passo avanti e implementa la cancellazione del tipo, che ha bisogno dell'heap anche l'allocazione. Quindi fondamentalmente si tratta solo di essere opportunisti!
A causa della cancellazione del tipo, std::shared_ptr
è in grado di supportare due cose:
void*
, ma è ancora in grado di eliminare correttamente gli oggetti durante la distruzione invocando correttamente il loro distruttore .Tutto a posto. Questo è tutto su come std::shared_ptr
funziona.
La domanda è: può std::unique_ptr
memorizzare oggetti come void*
? Ebbene, la risposta è sì , a patto di passare un appropriato deleter come argomento. Ecco una di queste dimostrazioni:
int main()
{
auto deleter = [](void const * data ) {
int const * p = static_cast<int const*>(data);
std::cout << *p << " located at " << p << " is being deleted";
delete p;
};
std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);
} //p will be deleted here, both p ;-)
Uscita ( demo online ):
959 located at 0x18aec20 is being deleted
Hai posto una domanda molto interessante nel commento:
Nel mio caso avrò bisogno di un deleter per la cancellazione dei tipi, ma sembra anche possibile (a costo di un'allocazione di heap). Fondamentalmente, questo significa che esiste in realtà un posto di nicchia per un terzo tipo di puntatore intelligente: un puntatore intelligente di proprietà esclusiva con cancellazione del tipo.
a cui @Steve Jessop ha suggerito la seguente soluzione,
In realtà non l'ho mai provato, ma forse potresti ottenerlo usando un appropriato
std::function
come tipo di deleter conunique_ptr
? Supponendo che funzioni effettivamente, hai finito, proprietà esclusiva e un deleter cancellato dal tipo.
Seguendo questo suggerimento, l'ho implementato (anche se non fa uso std::function
in quanto non sembra necessario):
using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;
template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
return unique_void_ptr(ptr, [](void const * data) {
T const * p = static_cast<T const*>(data);
std::cout << "{" << *p << "} located at [" << p << "] is being deleted.\n";
delete p;
});
}
int main()
{
auto p1 = unique_void(new int(959));
auto p2 = unique_void(new double(595.5));
auto p3 = unique_void(new std::string("Hello World"));
}
Uscita ( demo online ):
{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.
Spero che aiuti.
std::function
come deleter con unique_ptr
? Supponendo che funzioni effettivamente, hai finito, proprietà esclusiva e un deleter cancellato dal tipo.
Una delle ragioni è in uno dei tanti casi d'uso di a shared_ptr
- vale a dire come indicatore di vita o sentinella.
Questo è stato menzionato nella documentazione originale del boost:
auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
auto closure_target = { closure, std::weak_ptr<void>(pv) };
...
// store the target somewhere, and later....
}
void call_closure(closure_target target)
{
// test whether target of the closure still exists
auto lock = target.sentinel.lock();
if (lock) {
// if so, call the closure
target.closure();
}
}
Dov'è closure_target
qualcosa del genere:
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
Il chiamante registrerebbe una richiamata simile a questa:
struct active_object : std::enable_shared_from_this<active_object>
{
void start() {
event_emitter_.register_callback([this] { this->on_callback(); },
shared_from_this());
}
void on_callback()
{
// this is only ever called if we still exist
}
};
poiché shared_ptr<X>
è sempre convertibile in shared_ptr<void>
, event_emitter può ora essere beatamente inconsapevole del tipo di oggetto in cui sta richiamando.
Questa disposizione rilascia gli abbonati all'emittente dell'evento dall'obbligo di gestire i casi di attraversamento (cosa succede se la richiamata è in coda, in attesa di essere eseguita mentre active_object scompare?), E significa anche che non è necessario sincronizzare l'annullamento. weak_ptr<void>::lock
è un'operazione sincronizzata.
std::unique_ptr<void, D>
è ancora possibile fornendo un adattoD
.