Compartilhar via


Tutorial: como criar e usar instâncias shared_ptr

O tipo de shared_ptr é um ponteiro inteligente na biblioteca padrão C++ projetada para cenários em que mais de um proprietário precisa gerenciar o tempo de vida de um objeto. Depois de inicializar um shared_ptr, você pode copiá-lo, passá-lo pelo valor em argumentos de função e atribuí-lo a outras instâncias shared_ptr. Todas as instâncias apontam para o mesmo objeto e compartilham o acesso a um "bloco de controle" que incrementa e decrementa a contagem de referência sempre que um novo shared_ptr é adicionado, sai do escopo ou é redefinido. Quando a contagem de referência alcança zero, o bloco de controle exclui o recurso de memória e ele mesmo.

A ilustração a seguir mostra várias instâncias shared_ptr que apontam para um local da memória.

Diagrama mostrando duas instâncias de shared_ptr apontando para um local de memória.

O primeiro diagrama mostra um ponteiro compartilhado, P1, que aponta para uma instância MyClass, bem como um bloco de controle com contagem de ref = 1. O segundo diagrama mostra a adição de outro ponteiro compartilhado, P2, que também aponta para a instância MyClass e para o bloco de controle compartilhado, que agora tem uma contagem de ref de 2.

Exemplo de configuração

Os exemplos a seguir supõem que você incluiu os cabeçalhos necessários e declarou os tipos necessários, conforme mostrado aqui:

// 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
}

Exemplo 1

Sempre que possível, use a função make_shared para criar um shared_ptr quando o recurso de memória for criado pela primeira vez. make_shared é à prova de exceção. Ele usa a mesma chamada para alocar a memória para o bloco de controle e o recurso, que reduz a sobrecarga de construção. Se você não usar make_shared, deverá usar uma expressão new explícita para criar o objeto antes de passá-lo para o construtor shared_ptr. O exemplo a seguir mostra várias maneiras de declarar e inicializar um shared_ptr junto com um novo objeto.


// 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");

Exemplo 2

O exemplo a seguir mostra como declarar e inicializar instâncias shared_ptr que assumem a posse compartilhada de um objeto que já foi alocado por outro shared_ptr. Suponha que sp2 seja um shared_ptr inicializado.

//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);

Exemplo 3

shared_ptr também será útil em contêineres da Biblioteca Padrão do C++ quando você estiver usando algoritmos que copiam elementos. Você pode encapsular os elementos em um shared_ptr e, em seguida, copiá-lo em outros contêineres sabendo que a memória subjacente é válida somente pelo tempo em que for necessária. O exemplo a seguir mostra como usar o algoritmo remove_copy_if em instâncias shared_ptr em um vetor.

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

Exemplo 4

Você pode usar dynamic_pointer_cast, static_pointer_cast e const_pointer_cast para converter um shared_ptr. Essas funções se parecem com os operadores dynamic_cast, static_cast e const_cast. O exemplo a seguir mostra como testar o tipo derivado de cada elemento em um vetor de shared_ptr de classes base e copia os elementos e exibe informações sobre eles.

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

Exemplo 5

Você pode passar um shared_ptr para outra função das seguintes maneiras:

  • Passar o shared_ptr por valor. Isso invoca o construtor de cópia, incrementa a contagem de referência e torna o receptor um proprietário. Há uma pequena quantidade de sobrecarga nessa operação, que pode ser significativa dependendo de quantos objetos shared_ptr você está passando. Use essa opção quando o contrato de código implícito ou explícito entre o chamador e o receptor exigir que o receptor seja um proprietário.

  • Passar o shared_ptr por referência ou referência const. Nesse caso, a contagem de referência não é incrementada e o receptor pode acessar o ponteiro desde que o chamador não saia do escopo. Ou o receptor pode optar por criar um shared_ptr com base na referência e se tornar um proprietário compartilhado. Use esta opção quando o chamador não tiver conhecimento do receptor ou quando você precisar passar um shared_ptr e quiser evitar a operação de cópia por motivos de desempenho.

  • Passar o ponteiro subjacente ou uma referência ao objeto subjacente. Isso permite que o receptor use o objeto, mas não o habilita a compartilhar a propriedade ou estender o tempo de vida. Se o receptor criar um shared_ptr do ponteiro bruto, o novo shared_ptr será independente do original e não controlará o recurso subjacente. Use essa opção quando o contrato entre o chamador e o receptor claramente especificar que o chamador retém a propriedade do tempo de vida shared_ptr.

  • Ao decidir como passar um shared_ptr, determine se o receptor deve compartilhar a propriedade do recurso subjacente. Um "proprietário" é um objeto ou uma função que pode manter o recurso subjacente ativo pelo tempo necessário. Se o chamador precisar garantir que o receptor pode estender o tempo do ponteiro para além do seu tempo de vida (da função), use a primeira opção. Se não se importar se o receptor estende o tempo de vida, passe por referência e permita ou não que o receptor o copie.

  • Se você precisar conceder a uma função auxiliar acesso ao ponteiro subjacente e souber que a função auxiliar usa o ponteiro e retornar antes que a função de chamada retorne, essa função não precisará compartilhar a propriedade do ponteiro subjacente. Ela precisa apenas acessar o ponteiro dentro do tempo de vida do shared_ptr do chamador. Nesse caso, é seguro passar o shared_ptr por referência ou passar o ponteiro bruto ou uma referência ao objeto subjacente. Passar dessa maneira fornece um pequeno benefício de desempenho e também pode ajudar a expressar sua intenção de programação.

  • Às vezes, por exemplo em um std::vector<shared_ptr<T>>, você poderá precisar passar cada shared_ptr a um corpo da expressão lambda ou a um objeto de função. Se o lambda ou a função não armazenar o ponteiro, passe shared_ptr por referência para evitar invocar o construtor de cópia para cada 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));
}

Exemplo 6

O exemplo a seguir mostra como shared_ptr sobrecarrega vários operadores de comparação para habilitar comparações do ponteiro na memória que pertence às instâncias 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; 

Confira também

Ponteiros inteligentes (C++ moderno)