Condividi tramite


Procedura: Creare e usare istanze di shared_ptr

Il shared_ptr tipo è un puntatore intelligente nella libreria standard C++ progettata per scenari in cui più proprietari devono gestire la durata di un oggetto. Dopo avere inizializzato un oggetto shared_ptr, è possibile copiarlo, passarlo come valore negli argomenti di funzione e assegnarlo ad altre istanze di shared_ptr. Tutte le istanze puntano allo stesso oggetto e condividono l'accesso a un "blocco di controllo" che incrementa e decrementa il conteggio dei riferimenti ogni qualvolta un nuovo oggetto shared_ptr viene aggiunto, esce dall'ambito o viene reimpostato. Quando il conteggio dei riferimenti arriva a zero, il blocco di controllo elimina la risorsa di memoria e se stesso.

Nella figura che segue vengono mostrate diverse istanze di shared_ptr che puntano a una posizione di memoria.

Diagramma che mostra due istanze shared_ptr che puntano a una posizione di memoria.

Il primo diagramma mostra un puntatore condiviso, P1, che punta a un'istanza MyClass e a un blocco di controllo con conteggio riferimenti = 1. Il secondo diagramma mostra l'aggiunta di un altro puntatore condiviso, P2, che punta anche all'istanza myClass e al blocco di controllo condiviso, che ora ha un conteggio dei riferimenti pari a 2.

Esempio di impostazione

Gli esempi seguenti presuppongono che siano stati incluse le intestazioni necessarie e siano stati dichiarati i tipi necessari, come illustrato di seguito:

// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MediaAsset
{
    virtual ~MediaAsset() = default; // make it polymorphic
};

struct Song : public MediaAsset
{
    std::wstring artist;
    std::wstring title;
    Song(const std::wstring& artist_, const std::wstring& title_) :
        artist{ artist_ }, title{ title_ } {}
};

struct Photo : public MediaAsset
{
    std::wstring date;
    std::wstring location;
    std::wstring subject;
    Photo(
        const std::wstring& date_,
        const std::wstring& location_,
        const std::wstring& subject_) :
        date{ date_ }, location{ location_ }, subject{ subject_ } {}
};

using namespace std;

int main()
{
    // The examples go here, in order:
    // Example 1
    // Example 2
    // Example 3
    // Example 4
    // Example 6
}

Esempio 1

Quando possibile, usare la funzione make_shared per creare un elemento shared_ptr quando la risorsa di memoria viene creata per la prima volta. make_shared è indipendente dalle eccezioni. Usa la stessa chiamata per allocare memoria per un blocco di controllo e la risorsa e pertanto riduce il sovraccarico causato dalla costruzione. Se non si usa make_shared, è necessario usare un'espressione new esplicita per creare l'oggetto prima di passarlo al costruttore shared_ptr. Nell'esempio seguente vengono mostrati vari modi per dichiarare e inizializzare shared_ptr con un nuovo oggetto.


// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

// Ok, but slightly less efficient. 
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

// When initialization must be separate from declaration, e.g. class members, 
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

Esempio 2

Nell'esempio seguente viene illustrato come dichiarare e inizializzare shared_ptr istanze che assumono la proprietà condivisa di un oggetto allocato da un altro shared_ptroggetto . Si supponga che sp2 sia un oggetto shared_ptr inizializzato.

//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);

//Initialize via assignment. Increments ref count.
auto sp4 = sp2;

//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);

// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);

Esempio 3

shared_ptr è utile anche nei contenitori della libreria standard C++ quando si usano algoritmi che copiano elementi. È possibile eseguire il wrapping degli elementi in un oggetto shared_ptr, quindi copiarlo in altri contenitori, beninteso che la memoria sottostante è valida fino a quando serve e non oltre. Nell'esempio seguente viene illustrato come utilizzare l'algoritmo remove_copy_if su delle istanze di shared_ptr in un vettore.

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

Esempio 4

È possibile utilizzare dynamic_pointer_cast, static_pointer_cast e const_pointer_cast per eseguire il cast di shared_ptr. Queste funzioni sono simili agli operatori dynamic_cast, static_cast, const_cast. Nell'esempio seguente viene illustrato come testare il tipo derivato di ogni elemento in un vettore di shared_ptr classi di base e quindi copiare gli elementi e visualizzare informazioni su di essi.

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

Esempio 5

È possibile passare shared_ptr a un'altra funzione nei modi seguenti:

  • Passare shared_ptr per valore. Ciò richiama il costruttore di copia, incrementa il conteggio dei riferimenti e rende proprietario il chiamato. Questa operazione comporta un sovraccarico minimo, che può diventare significativo a seconda del numero di oggetti shared_ptr che vengono passati. Usare questa opzione quando il contratto di codice implicito o esplicito tra il chiamante e il chiamato richiede che il chiamato sia un proprietario.

  • Passare shared_ptr per riferimento o per riferimento const. In questo caso il conteggio dei riferimenti non viene incrementato e il chiamato può accedere al puntatore fino a quando il chiamante non esce dal proprio ambito. In alternativa, il chiamato può decidere di creare un oggetto shared_ptr basato sul riferimento e diventare un proprietario condiviso. Utilizzare questa opzione quando il chiamante non possiede alcuna informazione del chiamato o quando è necessario passare shared_ptr e si desidera evitare l'operazione di copia per motivi di prestazioni.

  • Passare il puntatore sottostante o un riferimento all'oggetto sottostante. Questo consente al chiamato di usare l'oggetto, ma non di condividere la proprietà o di estendere la durata. Se il chiamato crea un oggetto shared_ptr da un puntatore non elaborato, il nuovo oggetto shared_ptr è indipendente dall'originale e non controlla la risorsa sottostante. Utilizzare questa opzione quando il contratto tra il chiamante e il chiamato specifica chiaramente che il chiamante mantiene la proprietà del ciclo di vita di shared_ptr.

  • Quando si decide come passare un oggetto shared_ptr, determinare se il chiamato deve condividere la proprietà della risorsa sottostante. "Un proprietario" è un oggetto o una funzione che può mantenere la risorsa sottostante attiva fino a quando ne ha bisogno. Se il chiamante deve garantire che il chiamato può prolungare il ciclo di vita del puntatore oltre la durata della propria funzione, utilizzare la prima opzione. Se non è necessario che il chiamato estenda il ciclo di vita, allora passare per riferimento e consentire al chiamato di copiarlo o meno.

  • Se è necessario concedere a una funzione helper l'accesso al puntatore sottostante e si sa che la funzione helper usa il puntatore e restituisce prima che la funzione chiamante restituisca, tale funzione non deve condividere la proprietà del puntatore sottostante. Deve semplicemente accedere al puntatore entro il ciclo di vita dell'oggetto shared_ptr del chiamante. In questo caso, è possibile passare shared_ptr in base al riferimento oppure passare il puntatore non elaborato o un riferimento all'oggetto sottostante. Il passaggio di questo metodo offre un leggero miglioramento delle prestazioni e può inoltre aiutare a definire il proprio intento di programmazione.

  • Talvolta, ad esempio in un std::vector<shared_ptr<T>>, è necessario passare ogni shared_ptr a un corpo di espressione lambda o a un oggetto funzione denominata. Se l'espressione lambda o la funzione non memorizza il puntatore, passare shared_ptr in base al riferimento per evitare di chiamare il costruttore di copia per ogni elemento.

void use_shared_ptr_by_value(shared_ptr<int> sp);

void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);

void use_raw_pointer(int* p);
void use_reference(int& r);

void test() {
    auto sp = make_shared<int>(5);

    // Pass the shared_ptr by value.
    // This invokes the copy constructor, increments the reference count, and makes the callee an owner.
    use_shared_ptr_by_value(sp);

    // Pass the shared_ptr by reference or const reference.
    // In this case, the reference count isn't incremented.
    use_shared_ptr_by_reference(sp);
    use_shared_ptr_by_const_reference(sp);

    // Pass the underlying pointer or a reference to the underlying object.
    use_raw_pointer(sp.get());
    use_reference(*sp);

    // Pass the shared_ptr by value.
    // This invokes the move constructor, which doesn't increment the reference count
    // but in fact transfers ownership to the callee.
    use_shared_ptr_by_value(move(sp));
}

Esempio 6

Nell'esempio riportato di seguito viene illustrato come shared_ptr esegue l'overload di alcuni operatori di confronto per abilitare i confronti del puntatore sulla memoria che è posseduta dalle istanze di shared_ptr.


// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");

// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);    
shared_ptr<Song> p2(song2);

// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

Vedi anche

Puntatori intelligenti (C++ moderno)