Compartir a través de


Cómo: crear y utilizar instancias de shared_ptr

El tipo shared_ptr es puntero inteligente de la biblioteca estándar de C++ que está diseñado para escenarios en los que más de un propietario tiene que administrar la duración de un objeto. Después de inicializar shared_ptr, puede copiarlo, pasarlo por valor en argumentos de función y asignarlo a otras instancias de shared_ptr. Todas las instancias apuntan al mismo objeto y el acceso compartido a un "bloque de control" aumenta o disminuye el recuento de referencias siempre que un nuevo shared_ptr se agrega, se sale del ámbito o se restablece. Cuando el recuento de referencias llega a cero, el bloque de control elimina el recurso de memoria y se elimina a sí mismo.

En la ilustración siguiente se muestran varias instancias de shared_ptr que apuntan a una ubicación de memoria.

Diagrama en el que se muestran dos instancias de shared_ptr que apuntan a una ubicación en memoria.

El primer diagrama se muestra un puntero compartido, P1, que apunta a una instancia de MyClass, así como un bloque de control con recuento de referencias = 1. En el segundo diagrama se muestra la adición de otro puntero compartido, P2, que también apunta a la instancia de MyClass y al bloque de control compartido, que ahora tiene un recuento de referencias de 2.

Ejemplo de configuración

En los ejemplos siguientes se da por hecho que ha incluido los encabezados requeridos y declarado los tipos necesarios, tal como se muestra aquí:

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

Ejemplo 1

Siempre que sea posible, utilice la función make_shared para crear shared_ptr cuando se cree el recurso de memoria por primera vez. make_shared es seguro para excepciones. Utiliza la misma llamada para asignar memoria para el bloque de control y el recurso, lo que reduce la sobrecarga de la construcción. Si no utiliza make_shared, debe usar una expresión new explícita para crear el objeto antes de pasarlo al constructor de shared_ptr. En el ejemplo siguiente se muestran varias maneras de declarar e inicializar shared_ptr junto con un nuevo 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");

Ejemplo 2

En el ejemplo siguiente se muestra cómo declarar e inicializar las instancias de shared_ptr que adquieren la propiedad compartida de un objeto asignado por otra instancia de shared_ptr. Suponga que sp2 es un puntero 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);

Ejemplo 3

shared_ptr también es útil en los contenedores de la biblioteca estándar de C++ cuando se utilizan algoritmos que copian elementos. Puede ajustar los elementos en shared_ptr y copiarlos en otros contenedores, pero debe tener en cuenta que la memoria subyacente es válida mientras se necesita, y no más. En el ejemplo siguiente se muestra cómo usar el algoritmo remove_copy_if en las instancias de shared_ptr en un vector.

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

Ejemplo 4

Puede utilizar dynamic_pointer_cast, static_pointer_cast y const_pointer_cast para convertir shared_ptr. Estas funciones se parecen a los operadores dynamic_cast, static_cast y const_cast. En el ejemplo siguiente se muestra cómo probar el tipo derivado de cada elemento en un vector de shared_ptr de clases base y, después, copiar los elementos y mostrar información sobre ellos.

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

Ejemplo 5

Puede pasar shared_ptr a otra función de las maneras siguientes:

  • Pasar shared_ptr por valor. Esto invoca el constructor de copias, incrementa el recuento de referencias y convierte al destinatario en propietario. Hay una pequeña cantidad de sobrecarga en esta operación, que puede ser significativa en función del número de objetos shared_ptr que se pasen. Utilice esta opción cuando el contrato de código implícito o explícito entre el llamador y el destinatario requiera que el destinatario sea propietario.

  • Pasar shared_ptr por referencia o referencia const. En este caso, el recuento de referencias no se incrementa y el destinatario puede tener acceso al puntero siempre que el llamador no salga del ámbito. O bien, el destinatario puede decidir crear un puntero shared_ptr basado en la referencia y pasar a ser el propietario compartido. Utilice esta opción cuando el llamador no tiene conocimiento del destinatario o cuando se debe pasar shared_ptr y desea evitar la operación de copia por razones de rendimiento.

  • Pasar el puntero subyacente o una referencia al objeto subyacente. Esto permite al destinatario utilizar el objeto, pero no permite compartir la propiedad ni extender la duración. Si el destinatario crea un puntero shared_ptr a partir del puntero sin formato, el nuevo elemento shared_ptr será independiente del original y no controlará el recurso subyacente. Utilice esta opción cuando el contrato entre el llamador y el destinatario especifique claramente que el llamador conserva la propiedad de la duración de shared_ptr.

  • Cuando decida cómo pasar un puntero shared_ptr, determine si el destinatario tiene que compartir la propiedad del recurso subyacente. Un "propietario" es un objeto o función que puede mantener el recurso subyacente activo mientras lo necesite. Si el llamador tiene que garantizar que el destinatario pueda extender la vida del puntero más allá de la duración (de la función), utilice la primera opción. Si no le preocupa que el destinatario extienda la duración, páselo por referencia y permita que el destinatario lo copie o no.

  • Si tiene que proporcionar a una función de asistente el acceso al puntero subyacente y sabe que la función de asistente solo utilizará el puntero y volverá antes que la función de llamada, esa función no necesita compartir la propiedad del puntero subyacente. Solo debe tener acceso al puntero dentro de la duración de shared_ptr del llamador. En este caso, es seguro pasar el puntero shared_ptr por referencia o pasar el puntero sin formato o una referencia al objeto subyacente. Pasarlo de esta manera proporciona una pequeña ventaja de rendimiento y también puede ayudarle a expresar la intención de la programación.

  • A veces, por ejemplo en std::vector<shared_ptr<T>>, puede ser necesario pasar cada shared_ptr a un cuerpo de expresión lambda o a un objeto de función con nombre. Si la expresión lambda o la función no almacena el puntero, debe pasar el puntero shared_ptr por referencia para evitar llamar al constructor de copias 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));
}

Ejemplo 6

En el ejemplo siguiente se muestra cómo shared_ptr sobrecarga distintos operadores de comparación para habilitar las comparaciones de punteros en la memoria que pertenece a las instancias de 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; 

Consulte también

Punteros inteligentes (C++ moderno)