Namespaces
Variants

std:: shared_ptr

From es.cppreference.net
Memory management library
( exposition only* )
Allocators
Uninitialized memory algorithms
Constrained uninitialized memory algorithms
Memory resources
Uninitialized storage (until C++20)
( until C++20* )
( until C++20* )
( until C++20* )

Garbage collector support (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
Definido en el encabezado <memory>
template < class T > class shared_ptr ;
(desde C++11)

std::shared_ptr es un puntero inteligente que mantiene la propiedad compartida de un objeto a través de un puntero. Varios shared_ptr pueden poseer el mismo objeto. El objeto se destruye y su memoria se desasigna cuando ocurre cualquiera de los siguientes casos:

  • el último shared_ptr restante que posee el objeto es destruido;
  • el último shared_ptr restante que posee el objeto es asignado a otro puntero mediante operator= o reset() .

El objeto es destruido usando delete-expression o un custom deleter que se suministra a shared_ptr durante la construcción.

Un shared_ptr puede compartir la propiedad de un objeto mientras almacena un puntero a otro objeto. Esta característica puede utilizarse para apuntar a objetos miembro mientras se posee el objeto al que pertenecen. El puntero almacenado es al que se accede mediante get() , los operadores de desreferencia y los operadores de comparación. El puntero gestionado es el que se pasa al destructor cuando el contador de uso llega a cero.

Un shared_ptr también puede no poseer objetos, en cuyo caso se denomina vacío (un shared_ptr vacío puede tener un puntero almacenado no nulo si se utilizó el constructor de aliasing para crearlo).

Todas las especializaciones de shared_ptr cumplen con los requisitos de CopyConstructible , CopyAssignable , y LessThanComparable y son contextualmente convertibles a bool .

Todas las funciones miembro (incluyendo el constructor de copia y la asignación de copia) pueden ser llamadas por múltiples hilos en diferentes shared_ptr objetos sin sincronización adicional incluso si estos objetos son copias y comparten la propiedad del mismo objeto. Si múltiples hilos de ejecución acceden al mismo shared_ptr objeto sin sincronización y cualquiera de esos accesos utiliza una función miembro no constante de shared_ptr entonces ocurrirá una condición de carrera; el std::atomic<shared_ptr> puede utilizarse para prevenir la condición de carrera.

Contenidos

Tipos de miembros

Tipo de miembro Definición
element_type
T (hasta C++17)
std:: remove_extent_t < T > (desde C++17)
weak_type (desde C++17) std:: weak_ptr < T >

Funciones miembro

construye un nuevo shared_ptr
(función miembro pública)
destruye el objeto poseído si no hay más shared_ptr s que apunten a él
(función miembro pública)
asigna el shared_ptr
(función miembro pública)
Modificadores
reemplaza el objeto gestionado
(función miembro pública)
intercambia los objetos gestionados
(función miembro pública)
Observadores
devuelve el puntero almacenado
(función miembro pública)
desreferencia el puntero almacenado
(función miembro pública)
(C++17)
proporciona acceso indexado al array almacenado
(función miembro pública)
devuelve el número de objetos shared_ptr que referencian el mismo objeto gestionado
(función miembro pública)
(until C++20)
comprueba si el objeto gestionado está administrado únicamente por el objeto shared_ptr actual
(función miembro pública)
comprueba si el puntero almacenado no es nulo
(función miembro pública)
proporciona ordenación basada en propietario de punteros compartidos
(función miembro pública)
(C++26)
proporciona hashing basado en propietario de punteros compartidos
(función miembro pública)
proporciona comparación de igualdad basada en propietario de punteros compartidos
(función miembro pública)

Funciones no miembro

crea un puntero compartido que gestiona un nuevo objeto
(plantilla de función)
crea un puntero compartido que gestiona un nuevo objeto asignado mediante un asignador
(plantilla de función)
aplica static_cast , dynamic_cast , const_cast , o reinterpret_cast al puntero almacenado
(plantilla de función)
devuelve el eliminador del tipo especificado, si está poseído
(plantilla de función)
(eliminado en C++20) (eliminado en C++20) (eliminado en C++20) (eliminado en C++20) (eliminado en C++20) (C++20)
compara con otro shared_ptr o con nullptr
(plantilla de función)
envía el valor del puntero almacenado a un flujo de salida
(plantilla de función)
especializa el algoritmo std::swap
(plantilla de función)
especializa operaciones atómicas para std::shared_ptr
(plantilla de función)

Clases auxiliares

puntero compartido atómico
(especialización de plantilla de clase)
soporte de hash para std::shared_ptr
(especialización de plantilla de clase)

Guías de deducción (desde C++17)

Notas

La propiedad de un objeto solo puede compartirse mediante funciones de construcción o asignación que reciban otro objeto shared_ptr . Si se construye un nuevo shared_ptr utilizando únicamente el puntero subyacente en bruto mantenido por otro shared_ptr , este nuevo shared_ptr asumirá que no hay otras instancias de shared_ptr que posean el objeto que posee. Esto resultará (a menos que ocurra una asignación posterior) en la aplicación repetida del eliminador al mismo objeto durante la destrucción.

std::shared_ptr puede utilizarse con un tipo incompleto T . Sin embargo, el constructor a partir de un puntero en crudo ( template < class Y > shared_ptr ( Y * ) ) y la función miembro template < class Y > void reset ( Y * ) solo pueden llamarse con un puntero a un tipo completo (nótese que std::unique_ptr puede construirse a partir de un puntero en crudo a un tipo incompleto).

El T en std :: shared_ptr < T > puede ser un tipo de función: en este caso gestiona un puntero a función, en lugar de un puntero a objeto. Esto se utiliza a veces para mantener una biblioteca dinámica o un complemento cargado mientras alguna de sus funciones esté referenciada:

void del(void(*)()) {}
void fun() {}
int main()
{
    std::shared_ptr<void()> ee(fun, del);
    (*ee)();
}

Notas de implementación

En una implementación típica, shared_ptr contiene solo dos punteros:

  • el puntero almacenado (el devuelto por get() );
  • un puntero al control block .

El bloque de control es un objeto asignado dinámicamente que contiene:

  • ya sea un puntero al objeto gestionado o el objeto gestionado mismo;
  • el eliminador (con tipo borrado);
  • el asignador (con tipo borrado);
  • el número de shared_ptr s que poseen el objeto gestionado;
  • el número de weak_ptr s que hacen referencia al objeto gestionado.

Cuando shared_ptr se crea llamando a std::make_shared o std::allocate_shared , la memoria tanto para el bloque de control como para el objeto gestionado se crea con una única asignación. El objeto gestionado se construye in-situ en un miembro de datos del bloque de control. Cuando shared_ptr se crea mediante uno de los constructores de shared_ptr , el objeto gestionado y el bloque de control deben asignarse por separado. En este caso, el bloque de control almacena un puntero al objeto gestionado.

El puntero mantenido directamente por el shared_ptr es el que devuelve get() , mientras que el puntero/objeto mantenido por el bloque de control es el que será eliminado cuando el número de propietarios compartidos llegue a cero. Estos punteros no son necesariamente iguales.

El destructor de shared_ptr decrementa el número de propietarios compartidos del bloque de control. Si ese contador llega a cero, el bloque de control llama al destructor del objeto gestionado. El bloque de control no se desasigna a sí mismo hasta que el contador de std::weak_ptr también llegue a cero.

En implementaciones existentes, el número de weak pointers se incrementa ( [1] , [2] ) si existe un shared pointer al mismo bloque de control.

Para satisfacer los requisitos de seguridad de hilos, los contadores de referencia normalmente se incrementan usando un equivalente de std::atomic::fetch_add con std::memory_order_relaxed (el decremento requiere un ordenamiento más fuerte para destruir de forma segura el bloque de control).

Ejemplo

#include <chrono>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
using namespace std::chrono_literals;
struct Base
{
    Base() { std::cout << "Base::Base()\n"; }
    // Nota: el destructor no virtual está bien aquí
    ~Base() { std::cout << "Base::~Base()\n"; }
};
struct Derived : public Base
{
    Derived() { std::cout << "Derived::Derived()\n"; }
    ~Derived() { std::cout << "Derived::~Derived()\n"; }
};
void print(auto rem, std::shared_ptr<Base> const& sp)
{
    std::cout << rem << "\n\tget() = " << sp.get()
              << ", use_count() = " << sp.use_count() << '\n';
}
void thr(std::shared_ptr<Base> p)
{
    std::this_thread::sleep_for(987ms);
    std::shared_ptr<Base> lp = p; // seguro para hilos, aunque el
                                  // shared use_count se incrementa
    {
        static std::mutex io_mutex;
        std::lock_guard<std::mutex> lk(io_mutex);
        print("Local pointer in a thread:", lp);
    }
}
int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();
    print("Created a shared Derived (as a pointer to Base)", p);
    std::thread t1{thr, p}, t2{thr, p}, t3{thr, p};
    p.reset(); // liberar propiedad desde main
    print("Shared ownership between 3 threads and released ownership from main:", p);
    t1.join();
    t2.join();
    t3.join();
    std::cout << "All threads completed, the last one deleted Derived.\n";
}

Salida posible:

Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
	get() = 0x118ac30, use_count() = 1
Shared ownership between 3 threads and released ownership from main:
	get() = 0, use_count() = 0
Local pointer in a thread:
	get() = 0x118ac30, use_count() = 5
Local pointer in a thread:
	get() = 0x118ac30, use_count() = 4
Local pointer in a thread:
	get() = 0x118ac30, use_count() = 2
Derived::~Derived()
Base::~Base()
All threads completed, the last one deleted Derived.

Ejemplo

#include <iostream>
#include <memory>
struct MyObj
{
    MyObj() { std::cout << "MyObj construido\n"; }
    ~MyObj() { std::cout << "MyObj destruido\n"; }
};
struct Container : std::enable_shared_from_this<Container> // nota: herencia pública
{
    std::shared_ptr<MyObj> memberObj;
    void CreateMember() { memberObj = std::make_shared<MyObj>(); }
    std::shared_ptr<MyObj> GetAsMyObj()
    {
        // Usar un alias shared ptr para miembro
        return std::shared_ptr<MyObj>(shared_from_this(), memberObj.get());
    }
};
#define COUT(str) std::cout << '\n' << str << '\n'
#define DEMO(...) std::cout << #__VA_ARGS__ << " = " << __VA_ARGS__ << '\n'
int main()
{
    COUT("Creando contenedor compartido");
    std::shared_ptr<Container> cont = std::make_shared<Container>();
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    COUT("Creando miembro");
    cont->CreateMember();
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    COUT("Creando otro contenedor compartido");
    std::shared_ptr<Container> cont2 = cont;
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    DEMO(cont2.use_count());
    DEMO(cont2->memberObj.use_count());
    COUT("GetAsMyObj");
    std::shared_ptr<MyObj> myobj1 = cont->GetAsMyObj();
    DEMO(myobj1.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    DEMO(cont2.use_count());
    DEMO(cont2->memberObj.use_count());
    COUT("Copiando alias obj");
    std::shared_ptr<MyObj> myobj2 = myobj1;
    DEMO(myobj1.use_count());
    DEMO(myobj2.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    DEMO(cont2.use_count());
    DEMO(cont2->memberObj.use_count());
    COUT("Reiniciando cont2");
    cont2.reset();
    DEMO(myobj1.use_count());
    DEMO(myobj2.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    COUT("Reiniciando myobj2");
    myobj2.reset();
    DEMO(myobj1.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
    COUT("Restableciendo cont");
    cont.reset();
    DEMO(myobj1.use_count());
    DEMO(cont.use_count());
}

Salida:

Creando contenedor compartido
cont.use_count() = 1
cont->memberObj.use_count() = 0
Creando miembro
MyObj construido
cont.use_count() = 1
cont->memberObj.use_count() = 1
Creando otro contenedor compartido
cont.use_count() = 2
cont->memberObj.use_count() = 1
cont2.use_count() = 2
cont2->memberObj.use_count() = 1
GetAsMyObj
myobj1.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
cont2.use_count() = 3
cont2->memberObj.use_count() = 1
Copiando objeto alias
myobj1.use_count() = 4
myobj2.use_count() = 4
cont.use_count() = 4
cont->memberObj.use_count() = 1
cont2.use_count() = 4
cont2->memberObj.use_count() = 1
Reseteando cont2
myobj1.use_count() = 3
myobj2.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
Reseteando myobj2
myobj1.use_count() = 2
cont.use_count() = 2
cont->memberObj.use_count() = 1
Reseteando cont
myobj1.use_count() = 1
cont.use_count() = 0
MyObj destruido

Véase también

(C++11)
puntero inteligente con semántica de propiedad única de objetos
(plantilla de clase)
(C++11)
referencia débil a un objeto gestionado por std::shared_ptr
(plantilla de clase)