C++ Compartir propiedad (std :: shared_ptr)


Ejemplo

La plantilla de clase std::shared_ptr define un puntero compartido que puede compartir la propiedad de un objeto con otros punteros compartidos. Esto contrasta con std::unique_ptr que representa propiedad exclusiva.

El comportamiento de compartir se implementa a través de una técnica conocida como conteo de referencias, donde el número de punteros compartidos que apuntan al objeto se almacena a su lado. Cuando este conteo llega a cero, ya sea a través de la destrucción o la reasignación de la última instancia de std::shared_ptr , el objeto se destruye automáticamente.


// Creation: 'firstShared' is a shared pointer for a new instance of 'Foo'
std::shared_ptr<Foo> firstShared = std::make_shared<Foo>(/*args*/);

Para crear varios punteros inteligentes que compartan el mismo objeto, necesitamos crear otro shared_ptr que shared_ptr alias al primer puntero compartido. Aquí hay 2 formas de hacerlo:

std::shared_ptr<Foo> secondShared(firstShared);  // 1st way: Copy constructing
std::shared_ptr<Foo> secondShared;
secondShared = firstShared;                      // 2nd way: Assigning

Cualquiera de las formas anteriores convierte a secondShared un puntero compartido que comparte la propiedad de nuestra instancia de Foo con firstShared .

El puntero inteligente funciona como un puntero en bruto. Esto significa que puedes usar * para desreferenciarlos. El operador regular -> funciona:

secondShared->test(); // Calls Foo::test()

Finalmente, cuando el último alias shared_ptr sale del ámbito, se llama al destructor de nuestra instancia de Foo .

Advertencia: la construcción de shared_ptr puede shared_ptr una excepción bad_alloc cuando se deben asignar datos adicionales para la semántica de propiedad compartida. Si al constructor se le pasa un puntero normal, se supone que posee el objeto apuntado y llama al eliminador si se produce una excepción. Esto significa que shared_ptr<T>(new T(args)) no perderá un objeto T si shared_ptr<T> asignación de shared_ptr<T> . Sin embargo, es recomendable utilizar make_shared<T>(args) o allocate_shared<T>(alloc, args) , que permiten a la implementación optimizar la asignación de memoria.


Asignación de matrices ([]) utilizando shared_ptr

C ++ 11 C ++ 17

Desafortunadamente, no hay una forma directa de asignar Arrays usando make_shared<> .

Es posible crear matrices para shared_ptr<> usando new y std::default_delete .

Por ejemplo, para asignar una matriz de 10 enteros, podemos escribir el código como

shared_ptr<int> sh(new int[10], std::default_delete<int[]>());

Aquí es obligatorio especificar std::default_delete para asegurarse de que la memoria asignada se limpie correctamente utilizando delete[] .

Si conocemos el tamaño en tiempo de compilación, podemos hacerlo de esta manera:

template<class Arr>
struct shared_array_maker {};
template<class T, std::size_t N>
struct shared_array_maker<T[N]> {
  std::shared_ptr<T> operator()const{
    auto r = std::make_shared<std::array<T,N>>();
    if (!r) return {};
    return {r.data(), r};
  }
};
template<class Arr>
auto make_shared_array()
-> decltype( shared_array_maker<Arr>{}() )
{ return shared_array_maker<Arr>{}(); }

luego make_shared_array<int[10]> devuelve un shared_ptr<int> apunta a 10 ints, todo construido por defecto.

C ++ 17

Con C ++ 17, shared_ptr obtuvo soporte especial para tipos de arreglos. Ya no es necesario especificar el eliminador de matriz de forma explícita, y el puntero compartido se puede anular mediante el operador de índice de matriz [] :

std::shared_ptr<int[]> sh(new int[10]);
sh[0] = 42;

Los punteros compartidos pueden apuntar a un subobjeto del objeto que posee:

struct Foo { int x; };
std::shared_ptr<Foo> p1 = std::make_shared<Foo>();
std::shared_ptr<int> p2(p1, &p1->x);

Tanto p2 como p1 poseen el objeto de tipo Foo , pero p2 apunta a su miembro int x . Esto significa que si p1 queda fuera del alcance o se reasigna, el objeto Foo subyacente seguirá vivo, asegurándose de que p2 no cuelgue.


Importante: un shared_ptr solo se conoce a sí mismo y a todos los otros shared_ptr que se crearon con el constructor de alias. No conoce ningún otro puntero, incluidos todos los otros shared_ptr s creados con una referencia a la misma instancia de Foo :

Foo *foo = new Foo;
std::shared_ptr<Foo> shared1(foo);
std::shared_ptr<Foo> shared2(foo); // don't do this

shared1.reset(); // this will delete foo, since shared1
                 // was the only shared_ptr that owned it

shared2->test(); // UNDEFINED BEHAVIOR: shared2's foo has been
                 // deleted already!!

Propiedad Transferencia de shared_ptr

De forma predeterminada, shared_ptr incrementa el recuento de referencia y no transfiere la propiedad. Sin embargo, se puede hacer para transferir la propiedad usando std::move :

shared_ptr<int> up = make_shared<int>();
// Transferring the ownership
shared_ptr<int> up2 = move(up);
// At this point, the reference count of up = 0 and the
// ownership of the pointer is solely with up2 with reference count = 1