c++ - make_unique - shared_ptr example




¿Por qué es shared_ptr<void> legal, mientras que unique_ptr<void> está mal formado? (2)

Esto se debe a que std::shared_ptr implementa el borrado de tipos, mientras que std::unique_ptr no.

Dado que std::shared_ptr implementa type- std::shared_ptr , también admite otra propiedad interesante, a saber. no necesita el tipo del eliminador como argumento de tipo de plantilla para 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 tipos? Bueno, lo hace, porque tiene que admitir el recuento de referencias, y para admitir esto, tiene que asignar memoria del montón y dado que tiene que asignar memoria de todos modos, va un paso más allá e implementa el borrado de tipo, que necesita montón asignación también. ¡Así que básicamente es solo ser oportunista!

Debido a la std::shared_ptr de tipos, std::shared_ptr puede admitir dos cosas:

  • Puede almacenar objetos de cualquier tipo como void* , pero aún así puede eliminar los objetos en la destrucción correctamente invocando correctamente su destructor .
  • El tipo de eliminador no se pasa como argumento de tipo a la plantilla de clase, lo que significa un poco de libertad sin comprometer la seguridad de tipo .

Bien. De eso se trata std::shared_ptr .

Ahora la pregunta es, ¿puede std::unique_ptr almacenar objetos como void* ? Bueno, la respuesta es , 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 borrador de borrado de tipo, 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 una std::function apropiada como el tipo de unique_ptr con unique_ptr ? Supongamos que realmente funciona, entonces ya está hecho, propiedad exclusiva y un borrador de tipo borrado.

Siguiendo esta sugerencia, implementé esto (aunque no hace uso de 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.

La pregunta realmente encaja en el título: tengo curiosidad por saber cuál es la razón técnica de esta diferencia, pero también la razón.

std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;

Uno de los fundamentos está en uno de los muchos casos de uso de un shared_ptr , es decir, como un indicador de vida útil o centinela.

Esto se mencionó en la documentación original de impulso:

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();
    }
}

Donde closure_target es 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 como 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 que shared_ptr<X> siempre es convertible a shared_ptr<void> , el event_emitter ahora puede ser felizmente inconsciente del tipo de objeto al que está llamando de nuevo.

Este acuerdo libera a los suscriptores del emisor de eventos de la obligación de manejar los casos cruzados (¿qué sucede si la devolución de llamada ingresa en una cola, esperando ser procesada mientras active_object 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.





unique-ptr