C++ Condivisione della proprietà (std :: shared_ptr)


Esempio

Il modello di classe std::shared_ptr definisce un puntatore condiviso che è in grado di condividere la proprietà di un oggetto con altri puntatori condivisi. Questo contrasta con std::unique_ptr che rappresenta la proprietà esclusiva.

Il comportamento di condivisione è implementato attraverso una tecnica nota come conteggio dei riferimenti, in cui il numero di puntatori condivisi che puntano all'oggetto è memorizzato accanto a esso. Quando questo conteggio raggiunge lo zero, attraverso la distruzione o la riassegnazione dell'ultima istanza di std::shared_ptr , l'oggetto viene automaticamente distrutto.


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

Per creare più puntatori intelligenti che condividono lo stesso oggetto, dobbiamo creare un altro shared_ptr che alias il primo puntatore condiviso. Ecco due modi per farlo:

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

Uno dei due modi sopra rende secondShared un puntatore condiviso che condivide la proprietà della nostra istanza di Foo con firstShared .

Il puntatore intelligente funziona esattamente come un puntatore raw. Questo significa che puoi usare * per dereferenziarli. Anche l'operatore regolare -> funziona:

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

Infine, quando l'ultimo alias shared_ptr esce dall'ambito, viene chiamato il distruttore della nostra istanza di Foo .

Avvertenza: la costruzione di un file shared_ptr può generare un'eccezione bad_alloc quando vengono bad_alloc dati aggiuntivi per la semantica della proprietà condivisa. Se il costruttore riceve un puntatore regolare, assume di possedere l'oggetto puntato e chiama il deleter se viene lanciata un'eccezione. Ciò significa che shared_ptr<T>(new T(args)) non perderà un oggetto T se l'allocazione di shared_ptr<T> fallisce. Tuttavia, è consigliabile utilizzare make_shared<T>(args) o allocate_shared<T>(alloc, args) , che consentono all'implementazione di ottimizzare l'allocazione della memoria.


Allocazione di array ([]) utilizzando shared_ptr

C ++ 11 C ++ 17

Sfortunatamente, non esiste un modo diretto per allocare gli array usando make_shared<> .

È possibile creare array per shared_ptr<> usando new e std::default_delete .

Ad esempio, per allocare una matrice di 10 numeri interi, possiamo scrivere il codice come

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

La specifica di std::default_delete è obbligatoria qui per assicurarti che la memoria allocata sia correttamente ripulita usando delete[] .

Se conosciamo le dimensioni al momento della compilazione, possiamo farlo in questo modo:

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

quindi make_shared_array<int[10]> restituisce un shared_ptr<int> punta a 10 interi di default costruiti.

C ++ 17

Con C ++ 17, shared_ptr ottenuto un supporto speciale per i tipi di array. Non è più necessario specificare esplicitamente l'array-deleter e il puntatore condiviso può essere de-referenziato usando l'operatore [] dell'indice di array:

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

I puntatori condivisi possono puntare a un sottooggetto dell'oggetto che possiede:

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

Sia p2 che p1 possiedono l'oggetto di tipo Foo , ma p2 punta al suo membro int x . Ciò significa che se p1 esce dallo scope o viene riassegnato, l'oggetto Foo sottostante sarà ancora vivo, assicurando che p2 non penzoli.


Importante: un shared_ptr conosce solo se stesso e tutti gli altri shared_ptr creati con il costruttore alias. Non conosce altri puntatori, inclusi tutti gli altri shared_ptr creati con un riferimento alla stessa istanza di 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!!

Trasferimento di proprietà di shared_ptr

Per impostazione predefinita, shared_ptr incrementa il conteggio dei riferimenti e non trasferisce la proprietà. Tuttavia, può essere fatto per trasferire la proprietà 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