Пространства имён
Варианты
Действия

std::shared_ptr

Материал из cppreference.com
< cpp‎ | memory
 
 
Библиотека утилит
Языковая поддержка
Поддержка типов (базовые типы, RTTI)
Макросы тестирования функциональности библиотеки (C++20)    
Управление динамической памятью
Программные утилиты
Поддержка сопрограмм (C++20)
Вариативные функции
Трёхстороннее сравнение (C++20)
(C++20)
(C++20)(C++20)(C++20)(C++20)(C++20)(C++20)
Общие утилиты
Дата и время
Функциональные объекты
Библиотека форматирования (C++20)
(C++11)
Операторы отношения (устарело в C++20)
Целочисленные функции сравнения
(C++20)(C++20)(C++20)    
(C++20)
Операции обмена и типа
(C++14)
(C++11)
(C++11)
(C++11)
(C++17)
Общие лексические типы
(C++11)
(C++17)
(C++17)
(C++17)
(C++11)
(C++17)
(C++23)
Элементарные преобразования строк
(C++17)
(C++17)
 
Динамическое управление памятью
no section name
Ограниченные алгоритмы неинициализированной памяти
no section name
Поддержка сбора мусора
(C++11)(до C++23)
(C++11)(до C++23)
(C++11)(до C++23)
(C++11)(до C++23)
(C++11)(до C++23)
(C++11)(до C++23)



no section name
 
 
Определено в заголовочном файле <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
T (до C++17)
std::remove_extent_t<T> (начиная с C++17)
weak_type (начиная с C++17) std::weak_ptr<T>

[править] Функции-элементы

создаёт новый shared_ptr
(public функция-элемент) [править]
разрушает объект, которым владеет, если больше нет shared_ptr ссылающихся на него
(public функция-элемент) [править]
присваивает значение shared_ptr
(public функция-элемент) [править]
Модификаторы
заменяет управляемый объект
(public функция-элемент) [править]
обменивает управляемые объекты
(public функция-элемент) [править]
Наблюдатели
возвращает хранимый указатель
(public функция-элемент) [править]
разыменовывает сохранённый указатель
(public функция-элемент) [править]
обеспечивает индексированный доступ к сохранённому массиву
(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
(шаблон функции) [править]
выводит значение сохранённого указателя в выходной поток
(шаблон функции) [править]
специализация алгоритма std::swap
(шаблон функции) [править]
специализации атомарных операций для std::shared_ptr
(шаблон функции) [править]

[править] Вспомогательные классы

атомарный разделяемый указатель
(специализация шаблона класса) [править]
поддержка хэширования для 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)
слабая ссылка на объект, управляемый std::shared_ptr
(шаблон класса) [править]