Respuestas:
Es porque std::shared_ptr
implementa el borrado de tipos, mientras std::unique_ptr
que no lo hace.
Dado que std::shared_ptr
implementa el borrado de tipos, también admite otra propiedad interesante, a saber. sí no necesita el tipo de la Deleter como argumento de tipo de plantilla a la plantilla de clase. Mira sus declaraciones:
template<class T,class Deleter = std::default_delete<T> >
class unique_ptr;
que tiene Deleter
como parámetro de tipo, mientras que
template<class T>
class shared_ptr;
no lo tiene.
Ahora la pregunta es, ¿por qué shared_ptr
implementa el borrado de tipo? Bueno, lo hace, porque tiene que admitir el recuento de referencias, y para admitir esto, tiene que asignar memoria desde el montón y, dado que tiene que asignar memoria de todos modos, va un paso más allá e implementa el borrado de tipos, que necesita montón asignación también. ¡Así que básicamente es ser oportunista!
Debido al borrado de tipo, std::shared_ptr
puede admitir dos cosas:
void*
, pero aún puede eliminar los objetos en la destrucción correctamente invocando correctamente su destructor .Bien. Eso se trata de cómo std::shared_ptr
funciona.
Ahora la pregunta es, ¿se pueden std::unique_ptr
almacenar objetos como void*
? Bueno, la respuesta es sí , siempre que pase un eliminador adecuado como argumento. Aquí hay una de esas demostraciones:
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 ;-)
Salida ( demostración en línea ):
959 located at 0x18aec20 is being deleted
Hiciste una pregunta muy interesante en el comentario:
En mi caso, necesitaré un eliminador de borrado de tipos, pero también parece posible (a costa de una asignación de montón). Básicamente, ¿significa esto que en realidad hay un lugar de nicho para un tercer tipo de puntero inteligente: un puntero inteligente de propiedad exclusiva con borrado de tipo?
a lo que @Steve Jessop sugirió la siguiente solución,
En realidad, nunca he intentado esto, pero tal vez podrías lograrlo usando un tipo apropiado
std::function
como eliminador conunique_ptr
? Suponiendo que realmente funciona, entonces ya está hecho, propiedad exclusiva y un eliminador de tipo borrado.
Siguiendo esta sugerencia, implementé esto (aunque no hace uso std::function
ya que no parece necesario):
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"));
}
Salida ( demostración en línea ):
{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 ayude.
std::function
como eliminador con unique_ptr
? Suponiendo que realmente funciona, entonces ya está hecho, propiedad exclusiva y un eliminador de tipo borrado.
Uno de los fundamentos está en uno de los muchos casos de uso de un shared_ptr
, a saber, como indicador de por vida o centinela.
Esto se mencionó en la documentación original de 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();
}
}
¿Dónde closure_target
hay algo como esto?
struct closure_target {
std::function<void()> closure;
std::weak_ptr<void> sentinel;
};
La persona que llama registraría una devolución de llamada similar a esta:
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
}
};
debido a shared_ptr<X>
que siempre es convertible a shared_ptr<void>
, event_emitter ahora puede ser felizmente inconsciente del tipo de objeto al que está llamando.
Este arreglo libera a los suscriptores del emisor de eventos de la obligación de manejar los casos de cruce (¿qué pasa si la devolución de llamada está en una cola, esperando ser accionada mientras el objeto_activo desaparece?), Y también significa que no hay necesidad de sincronizar la cancelación de la suscripción. weak_ptr<void>::lock
es una operación sincronizada.
std::unique_ptr<void, D>
todavía es posible proporcionar un archivoD
.