Spec-Zone.ru › C++

std::shared_ptr

Определено в заголовочном файле <memory>
template< class T > class shared_ptr;
(с C++11)

std::shared_ptr — это умный указатель, который сохраняет совместное владение объектом через указатель. Несколько shared_ptr объектов могут владеть одним и тем же объектом. Объект уничтожается и его память освобождается, когда происходит одно из следующих событий:

  • уничтожается последний оставшийся shared_ptr объект, владеющий объектом;
  • последнему оставшемуся shared_ptr объекту, владеющему объектом, присваивается другой указатель с помощью operator= или reset().

Объект уничтожается с помощью выражения delete-expression или пользовательского обработчика удаления, который передаётся в shared_ptr во время создания.

shared_ptr может разделять владение объектом, храня указатель на другой объект. Эта функция может быть использована для указания на объекты-члены, одновременно владея объектом, к которому они относятся. Хранимый указатель является тем, к которому обращаются get(), операторы разыменования и сравнения. Управляемый указатель — это тот, который передаётся обработчику удаления, когда количество ссылок достигает нуля.

shared_ptr также может не владеть ни одним объектом, в этом случае он называется пустым (пустой shared_ptr может иметь не нулевой хранимый указатель, если для его создания использовался конструктор с алиасом).

Все специализации shared_ptr удовлетворяют требованиям CopyConstructible, CopyAssignable и LessThanComparable и являются контекстно преобразуемыми в bool.

Все члены функций (включая конструктор копирования и присваивание копированием) могут вызываться в нескольких потоках на разных экземплярах shared_ptr без дополнительной синхронизации, даже если эти экземпляры являются копиями и разделяют владение одним и тем же объектом. Если несколько потоков выполнения обращаются к одному экземпляру shared_ptr без синхронизации, и любое из этих обращений использует не-const член функции shared_ptr, то произойдёт гонка данных; можно использовать shared_ptr перегрузки атомарных функций для предотвращения гонки данных.

Типы членов

Тип члена Определение
element_type
T (до C++17)
std::remove_extent_t<T> (с C++17)
weak_type (с C++17) std::weak_ptr<T>

Члены функций

(конструктор)
создаёт новый shared_ptr
(публичная функция-член)
(деструктор)
уничтожает управляемый объект, если больше shared_ptr объектов не ссылаются на него
(публичная функция-член)
operator=
присваивает shared_ptr
(публичная функция-член)
Модификаторы
reset
заменяет управляемый объект
(публичная функция-член)
swap
меняет местами управляемые объекты
(публичная функция-член)
Наблюдатели
get
возвращает хранимый указатель
(публичная функция-член)
operator*operator->
разыменовывает хранимый указатель
(публичная функция-член)
operator[]
(C++17)
предоставляет индексированный доступ к хранимому массиву
(публичная функция-член)
use_count
возвращает количество shared_ptr объектов, ссылающихся на тот же управляемый объект
(публичная функция-член)
unique
(до C++20)
проверяет, является ли управляемый объект управляемым только текущим экземпляром shared_ptr
(публичная функция-член)
operator bool
проверяет, не равен ли хранимый указатель нулю
(публичная функция-член)
owner_before
обеспечивает упорядочение общих указателей на основе владельца
(публичная функция-член)
owner_hash
(C++26)
обеспечивает хеширование общих указателей на основе владельца
(публичная функция-член)
owner_equal
(C++26)
обеспечивает сравнение общих указателей на основе владельца
(публичная функция-член)

Внешние функции

make_sharedmake_shared_for_overwrite
(C++20)
создаёт указатель shared, управляющий новым объектом
(шаблон функции)
allocate_sharedallocate_shared_for_overwrite
(C++20)
создаёт указатель shared, управляющий новым объектом, выделенным с помощью аллокатора
(шаблон функции)
static_pointer_castdynamic_pointer_castconst_pointer_castreinterpret_pointer_cast
(C++17)
применяет static_cast, dynamic_cast, const_cast или reinterpret_cast к хранимому указателю
(шаблон функции)
get_deleter
возвращает удалитель указанного типа, если он принадлежит
(шаблон функции)
operator==operator!=operator<operator<=operator>operator>=operator<=>
(удалено в C++20)(удалено в C++20)(удалено в C++20)(удалено в C++20)(удалено в C++20)(C++20)
сравнивает с другим shared_ptr или с nullptr
(шаблон функции)
operator<<(std::shared_ptr)
выводит значение хранимого указателя в поток вывода
(шаблон функции)
std::swap(std::shared_ptr)
(C++11)
специализирует алгоритм std::swap
(шаблон функции)
std::atomic_is_lock_free(std::shared_ptr)std::atomic_load(std::shared_ptr)std::atomic_load_explicit(std::shared_ptr)std::atomic_store(std::shared_ptr)std::atomic_store_explicit(std::shared_ptr)std::atomic_exchange(std::shared_ptr)std::atomic_exchange_explicit(std::shared_ptr)std::atomic_compare_exchange_weak(std::shared_ptr)std::atomic_compare_exchange_strong(std::shared_ptr)std::atomic_compare_exchange_weak_explicit(std::shared_ptr)std::atomic_compare_exchange_strong_explicit(std::shared_ptr)
(устарело в C++20)
специализирует атомарные операции для std::shared_ptr
(шаблон функции)

Вспомогательные классы

std::atomic<std::shared_ptr>
(C++20)
атомарный указатель shared
(специализация шаблона класса)
std::hash<std::shared_ptr>
(C++11)
поддержка хеширования для std::shared_ptr
(специализация шаблона класса)

Указания по выводу типов (с C++17)

Примечания

Владение объектом может быть разделено только с другим shared_ptr путём копирующего построения или присваивания по значению другому shared_ptr. Создание нового shared_ptr с помощью необработанного базового указателя, принадлежащего другому shared_ptr, приведёт к неопределённому поведению.

std::shared_ptr может быть использован с неполным типом T. Однако конструктор из необработанного указателя (template<class Y> shared_ptr(Y*)) и член-функция template<class Y> void reset(Y*) могут быть вызваны только с указателем на полный тип (обратите внимание, что std::unique_ptr может быть создан из необработанного указателя на неполный тип).

Указатель в std::shared_ptr<T> может быть типом функции: в этом случае он управляет указателем на функцию, а не указателем на объект. Это иногда используется для сохранения загруженной динамической библиотеки или плагина до тех пор, пока к её функциям не будут обращаться:

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

Примечания по реализации

В типичной реализации shared_ptr содержит только два указателя:

  • хранимый указатель (возвращаемый get());
  • указатель на блок управления.

Блок управления — это динамически выделенный объект, содержащий:

  • либо указатель на управляемый объект, либо сам управляемый объект;
  • удалитель (типизированный);
  • аллокатор (типизированный);
  • количество shared_ptr владельцев управляемого объекта;
  • количество weak_ptr ссылок на управляемый объект.

Когда shared_ptr создаётся вызовом std::make_shared или std::allocate_shared, память как для блока управления, так и для управляемого объекта создаётся с помощью одного выделения. Управляемый объект создаётся на месте в члене блока управления. Когда shared_ptr создаётся с помощью одного из конструкторов shared_ptr, управляемый объект и блок управления должны быть выделены отдельно. В этом случае блок управления хранит указатель на управляемый объект.

Указатель, хранимый в shared_ptr непосредственно, является тем, который возвращается get(), в то время как указатель/объект, хранимый в блоке управления, — это тот, который будет удалён, когда количество общих владельцев достигнет нуля. Эти указатели необязательно равны.

Деструктор shared_ptr уменьшает количество общих владельцев блока управления. Если этот счётчик достигнет нуля, блок управления вызывает деструктор управляемого объекта. Блок управления не освобождает себя до тех пор, пока счётчик std::weak_ptr ссылок не достигнет нуля.

В существующих реализациях количество слабых указателей увеличивается ([1], [2]), если есть ссылка shared указателя на тот же блок управления.

Для обеспечения требований к потоковой безопасности счётчики ссылок обычно увеличиваются с помощью эквивалента std::atomic::fetch_add с std::memory_order_relaxed (уменьшение требует более строгого порядка для безопасного уничтожения блока управления).

Пример

#include <chrono>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
 
using namespace std::chrono_literals;
 
struct Base
{
    Base() { std::cout << "Base::Base()\n"; }
 
    // Note: non-virtual destructor is OK here
    ~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; // thread-safe, even though the
                                  // shared use_count is incremented
    {
        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(); // release ownership from 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";
}

Возможный вывод:

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.

Пример

#include <iostream>
#include <memory>
 
struct MyObj
{
    MyObj() { std::cout << "MyObj constructed\n"; }
 
    ~MyObj() { std::cout << "MyObj destructed\n"; }
};
 
struct Container : std::enable_shared_from_this<Container> // note: public inheritance
{
    std::shared_ptr<MyObj> memberObj;
 
    void CreateMember() { memberObj = std::make_shared<MyObj>(); }
 
    std::shared_ptr<MyObj> GetAsMyObj()
    {
        // Use an alias shared ptr for member
        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("Creating shared container");
    std::shared_ptr<Container> cont = std::make_shared<Container>();
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
 
    COUT("Creating member");
    cont->CreateMember();
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
 
    COUT("Creating another shared container");
    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("Copying 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("Resetting cont2");
    cont2.reset();
    DEMO(myobj1.use_count());
    DEMO(myobj2.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
 
    COUT("Resetting myobj2");
    myobj2.reset();
    DEMO(myobj1.use_count());
    DEMO(cont.use_count());
    DEMO(cont->memberObj.use_count());
 
    COUT("Resetting cont");
    cont.reset();
    DEMO(myobj1.use_count());
    DEMO(cont.use_count());
}

Вывод:

Creating shared container
cont.use_count() = 1
cont->memberObj.use_count() = 0
 
Creating member
MyObj constructed
cont.use_count() = 1
cont->memberObj.use_count() = 1
 
Creating another shared container
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
 
Copying alias obj
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
 
Resetting cont2
myobj1.use_count() = 3
myobj2.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
 
Resetting myobj2
myobj1.use_count() = 2
cont.use_count() = 2
cont->memberObj.use_count() = 1
 
Resetting cont
myobj1.use_count() = 1
cont.use_count() = 0
MyObj destructed

См. также

unique_ptr
(C++11)
интеллектуальный указатель с уникальной семантикой владения объектом
(шаблон класса)
weak_ptr
(C++11)
слабая ссылка на объект, управляемый std::shared_ptr
(шаблон класса)

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/memory/shared_ptr

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API