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 или пользовательского удалителя, переданного в shared_ptr при конструировании.

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

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

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

Все функции-члены (включая конструктор копирования и оператор присваивания копированием) могут вызываться несколькими потоками для разных объектов shared_ptr без дополнительной синхронизации, даже если эти объекты являются копиями и разделяют владение одним и тем же объектом. Если несколько потоков выполнения обращаются к одному объекту shared_ptr без синхронизации и хотя бы одно из обращений использует неконстантную функцию-член shared_ptr, возникает гонка данных; для её предотвращения можно использовать std::atomic<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
проверяет, что хранимый указатель не равен null
(функция-член общего доступа)
owner_before
обеспечивает упорядочение общих указателей на основе владельца
(функция-член общего доступа)
owner_hash
(C++26)
обеспечивает хеширование общих указателей на основе владельца
(функция-член общего доступа)
owner_equal
(C++26)
обеспечивает сравнение общих указателей на равенство на основе владельца
(функция-член общего доступа)

Функции, не являющиеся членами

make_sharedmake_shared_for_overwrite
(C++20)
создаёт общий указатель, управляющий новым объектом
(шаблон функции)
allocate_sharedallocate_shared_for_overwrite
(C++20)
создаёт общий указатель, управляющий новым объектом, выделенным с помощью аллокатора
(шаблон функции)
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)(удалено в C++26)
специализирует атомарные операции для std::shared_ptr
(шаблон функции)

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

std::atomic<std::shared_ptr>
(C++20)
атомарный общий указатель
(специализация шаблона класса)
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 можно сконструировать из необработанного указателя на неполный тип).

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

void del(void(*)()) {}

void fun() {}

int main()
{
    std::shared_ptr<void()> ee(fun, del);
    (*ee)();
}
Макрос проверки поддержки функции Значение Стандарт Функция
__cpp_lib_constexpr_memory 202506L (C++26) Constexpr std::shared_ptr

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

В типичной реализации 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]), если существует общий указатель на тот же блок управления.

Для соблюдения требований потокобезопасности счётчики ссылок обычно увеличиваются с помощью аналога 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/cpp/memory/shared_ptr

Spec-Zone.ru

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