Namespaces
Variants

std:: shared_ptr

From cppreference.net
Memory management library
( exposition only* )
Allocators
Uninitialized memory algorithms
Constrained uninitialized memory algorithms
Memory resources
Uninitialized storage (until C++20)
( until C++20* )
( until C++20* )
( until C++20* )

Garbage collector support (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
(C++11) (until C++23)
Определено в заголовочном файле <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
T (до C++17)
std:: remove_extent_t < T > (начиная с C++17)
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 на основе владельца
(публичная функция-член)
предоставляет сравнение на равенство 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
(шаблон функции)
выводит значение сохраненного указателя в выходной поток
(шаблон функции)
специализирует алгоритм std::swap
(шаблон функции)
специализирует атомарные операции для std::shared_ptr
(шаблон функции)

Вспомогательные классы

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