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 |
|
||||
weak_type (начиная с C++17) | std::weak_ptr<T> |
Функции-члены
создаёт новый объект shared_ptr (функция-член общего доступа) |
|
уничтожает принадлежащий объект, если на него больше не ссылаются объекты shared_ptr (функция-член общего доступа) |
|
присваивает значение объекту shared_ptr (функция-член общего доступа) |
|
Модификаторы |
|
| заменяет управляемый объект (функция-член общего доступа) |
|
| меняет управляемые объекты местами (функция-член общего доступа) |
|
Наблюдатели |
|
| возвращает хранимый указатель (функция-член общего доступа) |
|
| разыменовывает хранимый указатель (функция-член общего доступа) |
|
|
(C++17) | обеспечивает доступ к хранимому массиву по индексу (функция-член общего доступа) |
возвращает количество объектов shared_ptr, ссылающихся на один и тот же управляемый объект (функция-член общего доступа) |
|
|
(до C++20) | проверяет, управляется ли объект только текущим объектом shared_ptr (функция-член общего доступа) |
| проверяет, что хранимый указатель не равен null (функция-член общего доступа) |
|
| обеспечивает упорядочение общих указателей на основе владельца (функция-член общего доступа) |
|
|
(C++26) | обеспечивает хеширование общих указателей на основе владельца (функция-член общего доступа) |
|
(C++26) | обеспечивает сравнение общих указателей на равенство на основе владельца (функция-член общего доступа) |
Функции, не являющиеся членами
|
(C++20) | создаёт общий указатель, управляющий новым объектом (шаблон функции) |
|
(C++20) | создаёт общий указатель, управляющий новым объектом, выделенным с помощью аллокатора (шаблон функции) |
|
(C++17) | применяет static_cast, dynamic_cast, const_cast или reinterpret_cast к хранимому указателю (шаблон функции) |
| возвращает удалитель указанного типа, если объект принадлежит указателю (шаблон функции) |
|
|
(удалён в C++20)(удалён в C++20)(удалён в C++20)(удалён в C++20)(удалён в C++20)(C++20) | сравнивает с другим объектом shared_ptr или с nullptr (шаблон функции) |
| выводит значение хранимого указателя в выходной поток (шаблон функции) |
|
|
(C++11) | специализирует алгоритм std::swap (шаблон функции) |
|
(устарело в C++20)(удалено в C++26) | специализирует атомарные операции для std::shared_ptr (шаблон функции) |
Вспомогательные классы
|
(C++20) | атомарный общий указатель (специализация шаблона класса) |
|
(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
См. также
|
(C++11) | интеллектуальный указатель с семантикой единоличного владения объектом (шаблон класса) |
|
(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