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
без синхронизации и любое из этих обращений использует неконстантную функцию-член
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
, ссылающихся на тот же управляемый объект
(публичная функция-член) |
|
|
(until C++20)
|
проверяет, управляется ли объект только текущим объектом
shared_ptr
(публичная функция-член) |
|
проверяет, не является ли сохраненный указатель нулевым
(публичная функция-член) |
|
|
предоставляет упорядочивание shared pointers на основе владельца
(публичная функция-член) |
|
|
(C++26)
|
предоставляет хеширование shared pointers на основе владельца
(публичная функция-член) |
|
(C++26)
|
предоставляет сравнение на равенство shared pointers на основе владельца
(публичная функция-член) |
Функции, не являющиеся членами класса
|
создает умный указатель, который управляет новым объектом
(шаблон функции) |
|
|
создает умный указатель, который управляет новым объектом, выделенным с использованием аллокатора
(шаблон функции) |
|
|
применяет
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
(шаблон функции) |
специализирует атомарные операции для
std::shared_ptr
(шаблон функции) |
Вспомогательные классы
|
(C++20)
|
атомарный умный указатель shared_ptr
(специализация шаблона класса) |
|
(C++11)
|
поддержка хеширования для
std::shared_ptr
(специализация шаблона класса) |
Руководства по выводу типа (начиная с C++17)
Примечания
Владение объектом может быть разделено только через функции конструктора или присваивания, которые принимают другой объект
shared_ptr
. Если новый
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)(); }
Примечания к реализации
В типичной реализации
shared_ptr
содержит только два указателя:
- сохранённый указатель (возвращаемый get() );
- указатель на control block .
Блок управления — это динамически выделяемый объект, который содержит:
- либо указатель на управляемый объект, либо сам управляемый объект;
- удалитель (стирание типа);
- аллокатор (стирание типа);
-
количество
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 pointer на тот же блок управления.
Для обеспечения требований потокобезопасности, счётчики ссылок обычно увеличиваются с использованием эквивалента 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"; } // Примечание: невиртуальный деструктор здесь допустим ~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; // потокобезопасно, даже несмотря на то, что // общий use_count увеличивается { static std::mutex io_mutex; std::lock_guard<std::mutex> lk(io_mutex); print("Локальный указатель в потоке:", lp); } } int main() { std::shared_ptr<Base> p = std::make_shared<Derived>(); print("Создан общий Derived (как указатель на Base)", p); std::thread t1{thr, p}, t2{thr, p}, t3{thr, p}; p.reset(); // освободить владение из main print("Общее владение между 3 потоками и освобождение владения из main:", p); t1.join(); t2.join(); t3.join(); std::cout << "Все потоки завершены, последний удалил Derived.\n"; }
Возможный вывод:
Base::Base() Derived::Derived() Создан общий Derived (как указатель на Base) get() = 0x118ac30, use_count() = 1 Общее владение между 3 потоками и освобождение владения из main: get() = 0, use_count() = 0 Локальный указатель в потоке: get() = 0x118ac30, use_count() = 5 Локальный указатель в потоке: get() = 0x118ac30, use_count() = 4 Локальный указатель в потоке: get() = 0x118ac30, use_count() = 2 Derived::~Derived() Base::~Base() Все потоки завершены, последний удалил Derived.
Пример
#include <iostream> #include <memory> struct MyObj { MyObj() { std::cout << "MyObj сконструирован\n"; } ~MyObj() { std::cout << "MyObj уничтожен\n"; } }; struct Container : std::enable_shared_from_this<Container> // примечание: публичное наследование { std::shared_ptr<MyObj> memberObj; void CreateMember() { memberObj = std::make_shared<MyObj>(); } std::shared_ptr<MyObj> GetAsMyObj() { // Использовать псевдоним shared_ptr для члена класса 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("Создание общего контейнера"); std::shared_ptr<Container> cont = std::make_shared<Container>(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Создание члена"); cont->CreateMember(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Создание другого общего контейнера"); 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("Копирование псевдонима объекта"); 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("Сброс cont2"); cont2.сброс(); DEMO(myobj1.use_count()); DEMO(myobj2.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Сброс myobj2"); myobj2.сброс(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Сброс счетчика"); cont.сброс(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); }
Вывод:
Создание общего контейнера cont.use_count() = 1 cont->memberObj.use_count() = 0 Создание члена MyObj сконструирован cont.use_count() = 1 cont->memberObj.use_count() = 1 Создание другого общего контейнера cont.use_count() = 2 cont->memberObj.use_count() = 1 cont2.use_count() = 2 cont2->memberObj.use_count() = 1 Получение как MyObj myobj1.use_count() = 3 cont.use_count() = 3 cont->memberObj.use_count() = 1 cont2.use_count() = 3 cont2->memberObj.use_count() = 1 Копирование объекта-псевдонима 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 Сброс cont2 myobj1.use_count() = 3 myobj2.use_count() = 3 cont.use_count() = 3 cont->memberObj.use_count() = 1 Сброс myobj2 myobj1.use_count() = 2 cont.use_count() = 2 cont->memberObj.use_count() = 1 Сброс cont myobj1.use_count() = 1 cont.use_count() = 0 MyObj уничтожен
Смотрите также
|
(C++11)
|
умный указатель с семантикой уникального владения объектом
(шаблон класса) |
|
(C++11)
|
слабая ссылка на объект, управляемый
std::shared_ptr
(шаблон класса) |