Respostas:
É porque std::shared_ptr
implementa o apagamento de tipo, enquanto std::unique_ptr
não.
Uma vez que std::shared_ptr
implementa o apagamento de tipo, ele também suporta outra propriedade interessante, viz. ele não precisa do tipo de deletador como argumento de tipo de modelo para o modelo de classe. Veja suas declarações:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
que tem Deleter
como parâmetro de tipo, enquanto
template<class T>
class shared_ptr;
não tem.
Agora a questão é: por que shared_ptr
implementar a eliminação de tipo? Bem, ele faz isso, porque tem que suportar a contagem de referência, e para suportar isso, ele tem que alocar memória do heap e, uma vez que tem que alocar memória de qualquer maneira, ele vai um passo adiante e implementa o apagamento de tipo - que precisa do heap alocação também. Então, basicamente, é apenas ser oportunista!
Devido ao apagamento de tipo, std::shared_ptr
é capaz de suportar duas coisas:
void*
, mas ainda assim é capaz de excluir os objetos em destruição apropriadamente invocando corretamente seu destruidor .Tudo bem. Isso é tudo sobre como std::shared_ptr
funciona.
Agora a questão é: pode std::unique_ptr
armazenar objetos como void*
? Bem, a resposta é sim - desde que você passe um deletor adequado como argumento. Aqui está uma dessas demonstrações:
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 ;-)
Resultado ( demonstração online ):
959 located at 0x18aec20 is being deleted
Você fez uma pergunta muito interessante no comentário:
No meu caso, vou precisar de um apagador de apagamento de tipo, mas também parece possível (ao custo de alguma alocação de heap). Basicamente, isso significa que existe realmente um nicho para um terceiro tipo de ponteiro inteligente: um ponteiro inteligente de propriedade exclusiva com eliminação de tipo.
para o qual @Steve Jessop sugeriu a seguinte solução,
Na verdade, eu nunca tentei isso, mas talvez você pudesse conseguir isso usando um apropriado
std::function
como o tipo de exclusão comunique_ptr
? Supondo que isso realmente funcione, então você está pronto, propriedade exclusiva e um apagador apagado.
Seguindo essa sugestão, implementei (embora não faça uso std::function
, pois não parece necessário):
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"));
}
Resultado ( demonstração online ):
{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.
Espero que ajude.
std::function
como o tipo de exclusão com unique_ptr
? Supondo que isso realmente funcione, então você está pronto, propriedade exclusiva e um apagador apagado.
Uma das razões está em um dos muitos casos de uso de a shared_ptr
- a saber, como um indicador vitalício ou sentinela.
Isso foi mencionado na documentação original do 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();
}
}
Onde closure_target
está algo assim:
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
O chamador registraria um retorno de chamada mais ou menos assim:
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
}
};
porque shared_ptr<X>
é sempre conversível shared_ptr<void>
, o event_emitter pode agora felizmente não estar ciente do tipo de objeto para o qual está chamando de volta.
Esse arranjo libera os assinantes para o emissor do evento da obrigação de lidar com casos cruzados (e se o retorno de chamada estiver em uma fila, esperando para ser acionado enquanto o objeto_ativo vai embora?), E também significa que não há necessidade de sincronizar o cancelamento da assinatura. weak_ptr<void>::lock
é uma operação sincronizada.
std::unique_ptr<void, D>
ainda é possível fornecendo um adequadoD
.