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 <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> 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 thr(std::shared_ptr<Base> p) { std::shared_ptr<Base> lp = p; // потокобезопасно, даже если // общий use_count инкрементируется { static std::mutex io_mutex; std::lock_guard<std::mutex> lk(io_mutex); std::cout << "локальный указатель в потоке:\n" << " lp.get() = " << lp.get() << ", lp.use_count() = " << lp.use_count() << '\n'; } } int main() { std::shared_ptr<Base> p = std::make_shared<Derived>(); std::cout << "Создан общий Derived (как указатель на Base)\n" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << '\n'; std::thread t1(thr, p), t2(thr, p), t3(thr, p); p.reset(); // освободить владение функцией main std::cout << "Создать совместное владение между 3-мя потоками и освободить\n" << "владение функцией main:\n" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << '\n'; t1.join(); t2.join(); t3.join(); std::cout << "Все потоки завершены, последним удалён Derived\n"; }
Возможный вывод:
Base::Base() Derived::Derived() Создан общий Derived (как указатель на Base) p.get() = 0x2299b30, p.use_count() = 1 Создать совместное владение между 3-мя потоками и освободить владение функцией main: p.get() = 0, p.use_count() = 0 локальный указатель в потоке: lp.get() = 0x2299b30, lp.use_count() = 5 локальный указатель в потоке: lp.get() = 0x2299b30, lp.use_count() = 3 локальный указатель в потоке: lp.get() = 0x2299b30, lp.use_count() = 2 Derived::~Derived() Base::~Base() Все потоки завершены, последним удалён Derived
[править] Смотрите также
| (C++11) |
умный указатель с уникальной семантикой владения объектом (шаблон класса) |
| (C++11) |
слабая ссылка на объект, управляемый std::shared_ptr (шаблон класса) |

