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.
std::shared_ptr может разделять владение объектом и в то же время хранить указатель на другой объект. Это позволяет владеть объектом и в то же время указывать на элемент этого объекта. Хранимый указатель это тот, к которому обращается get(), операторы разыменования и сравнения. Управляемый указатель это тот, который будет передан функции удаления объекта, когда счётчик владения достигнет нуля.
shared_ptr может не владеть ни одним объектом, в этом случае он называется пустым (пустой shared_ptr может иметь ненулевой сохранённый указатель, если был использован псевдонимный конструктор).
Все специализации shared_ptr отвечают требованиям CopyConstructible, CopyAssignable и LessThanComparable, а также неявно преобразуются в bool.
Все функции-элементы (включая конструктор копирования и присваивание копированием) могут быть вызваны в нескольких потоках разными экземплярами shared_ptr без дополнительной синхронизации, даже если эти экземпляры копируют и владеют одним и тем же объектом. Если несколько потоков обращаются к одному и тому же экземпляру shared_ptr без синхронизации и какое-либо обращение происходит через неконстантную функцию-элемент shared_ptr, тогда произойдёт гонка данных; для предотвращения гонки данных можно использовать перегрузку атомарных функций для shared_ptr.
Типы-элемент
| Тип-элемент | Определение | ||||
element_type
|
| ||||
weak_type (начиная с C++17)
|
std::weak_ptr<T>
| ||||
Функции-элементы
создаёт новый shared_ptr (public функция-элемент) | |
разрушает объект, которым владеет, если больше нет shared_ptr ссылающихся на него (public функция-элемент) | |
присваивает значение shared_ptr (public функция-элемент) | |
Модификаторы | |
| заменяет управляемый объект (public функция-элемент) | |
| обменивает управляемые объекты (public функция-элемент) | |
Наблюдатели | |
| возвращает хранимый указатель (public функция-элемент) | |
| разыменовывает сохранённый указатель (public функция-элемент) | |
(C++17) |
обеспечивает индексированный доступ к сохранённому массиву (public функция-элемент) |
возвращает количество объектов shared_ptr, ссылающихся на один и тот же управляемый объект (public функция-элемент) | |
(до C++20) |
проверяет, управляется ли управляемый объект только текущим экземпляром shared_ptr (public функция-элемент) |
| проверяет, не является ли сохранённый указатель нулевым (public функция-элемент) | |
| обеспечивает упорядочивание общих указателей на основе владельца (public функция-элемент) | |
Функции, не являющиеся элементами
| создаёт общий указатель, который управляет новым объектом (шаблон функции) | |
| создаёт общий указатель, который управляет новым объектом, выделенным с помощью аллокатора (шаблон функции) | |
| применяет 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) |
атомарный разделяемый указатель (специализация шаблона класса) |
(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)();
}
Примечания по реализации
В типичной реализации, std::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"; }
// Примечание: здесь можно использовать невиртуальный деструктор
~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()
{
// Использовать псевдоним std::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( "Копирование псевдонима 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( "Сброс cont2" );
cont2.reset();
DEMO( myobj1.use_count() );
DEMO( myobj2.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Сброс myobj2" );
myobj2.reset();
DEMO( myobj1.use_count() );
DEMO( cont.use_count() );
DEMO( cont->memberObj.use_count() );
COUT( "Сброс cont" );
cont.reset();
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
GetAsMyObj
myobj1.use_count() = 3
cont.use_count() = 3
cont->memberObj.use_count() = 1
cont2.use_count() = 3
cont2->memberObj.use_count() = 1
Копирование псевдонима 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
Сброс 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 (шаблон класса) |