Урок №194. std::shared_ptr

  Юрий  | 

    | 

  Обновл. 31 Дек 2018  | 

 1166

В отличие от std::unique_ptr, который предназначен для единоличного владения и управления переданным ему ресурсом/объектом, std::shared_ptr предназначен для совместного владения несколькими умными указателями одним динамически выделенным ресурсом.

Это означает, что вы можете иметь несколько std::shared_ptr, указывающие на один и тот же ресурс. std::shared_ptr отслеживает количество владельцев у каждого полученного ресурса. До тех пор, пока хотя бы один std::shared_ptr владеет ресурсом, этот ресурс не будет уничтожен, даже если удалить все остальные std::shared_ptr (который также владеют этим ресурсом). Как только последний std::shared_ptr, владеющий ресурсом, выйдет из области видимости (или ему передадут другой ресурс для управления), тогда ресурс будет уничтожен.

Как и std::unique_ptr, std::shared_ptr находится в заголовочном файле memory.

Результат выполнения программы выше:

Item acquired
Killing one shared pointer
Killing another shared pointer
Item destroyed

Здесь мы динамически выделяем объект класса Item и передаём его указателю std::shared_ptr с именем ptr1. Внутри вложенного блока функции main() мы используем семантику копирования (которая разрешена в std::shared_ptr, так как одним ресурсом могут владеть сразу несколько умных указателей) для создания второго std::shared_ptr (ptr2), который указывает на тот же выделенный Item. Когда ptr2 выходит из области видимости, Item не уничтожается, так как ptr1 по-прежнему указывает на него. Когда ptr1 выходит из области видимости, он замечает, что больше нет std::shared_ptr, которые бы указывали на Item, и удаляет Item.

Обратите внимание, мы создали второй умный указатель из первого умного указателя (используя оператор присваивания копированием). Это важно.

Рассмотрим следующую программу:

Результат:

Item acquired
Killing one shared pointer
Item destroyed
Killing another shared pointer
Item destroyed

И затем «Бум!» — генерация исключения (по крайней мере, на компьютере автора).

Разница здесь в том, что мы создали два отдельных, независимых друг от друга, умных указателя std::shared_ptr. Как следствие, хотя они оба указывают на один и тот же Item, они не знают о существовании друг друга. Когда ptr2 выходит из области видимости, он думает, что является единственным владельцем Item, поэтому уничтожает его. Когда позже ptr1 выходит из области видимости, он думает также и пытается снова удалить (уже удаленный) Item. Бум!

К счастью, этого легко избежать, используя семантику копирования для создания нескольких умных указателей, указывающих на один динамически выделенный ресурс.

Правило: Всегда выполняйте копирование существующего std::shared_ptr, если вам нужно более одного std::shared_ptr, указывающего на один и тот же динамически выделенный ресурс.

std::make_shared

Как и std::make_unique() можно использовать для создания std::unique_ptr в C++14, так и std::make_shared() можно (и нужно) использовать для создания std::shared_ptr. Функцию std::make_shared() добавили в C++11.

Давайте перепишем первую программу из этого урока с использованием std::make_shared():

Причины использования std::make_shared() такие же, как и причины использования std::make_unique(): проще, безопаснее и производительнее за счет того, что std::shared_ptr отслеживает, сколько умных указателей владеют ресурсом.

Детали реализации std::shared_ptr


В отличие от std::unique_ptr, который использует внутри (под капотом) один указатель, std::shared_ptr использует внутри два указателя. Один указывает на передаваемый ресурс, а второй указывает на «блок управления» — динамически выделенный объект, который отслеживает кучу разных вещей, включая и то, сколько std::shared_ptr одновременно указывают на каждый полученный ресурс. При создании std::shared_ptr с помощью конструктора std::shared_ptr, память для полученного ресурса и блока управления (которого также создает конструктор) выделяется отдельно. Однако в std::make_shared() это оптимизировано в единое выделение памяти, что, соответственно, повышает производительность.

Это также объясняет, почему независимое создание двух std::shared_ptr, указывающих на один и тот же ресурс, приводит к проблемам. Каждый std::shared_ptr имеет один указатель, указывающий на полученный ресурс. Однако каждый std::shared_ptr еще и независимо выделяет свой собственный блок управления, который сообщает указателю, что он является единственным «владельцем» полученного ресурса (даже если это не так). Таким образом, когда этот std::shared_ptr выходит из области видимости, он уничтожает ресурс, которым владеет, не осознавая того, что могут быть еще другие std::shared_ptr, которые владеют этим же ресурсом.

Однако, когда std::shared_ptr клонируется с использованием семантики копирования, данные в блоке управления соответствующим образом обновляются и указывают, что появился еще один std::shared_ptr, указывающий на полученный ресурс.

Создание std::shared_ptr из std::unique_ptr

std::unique_ptr может быть конвертирован в std::shared_ptr через специальный конструктор std::shared_ptr, который принимает std::unique_ptr в качестве r-value. Таким образом, содержимое std::unique_ptr перемещается в std::shared_ptr.

Однако std::shared_ptr нельзя безопасно конвертировать в std::unique_ptr. Поэтому, если вы хотите создать функцию, которая будет возвращать умный указатель, вам лучше возвращать std::unique_ptr и затем присваивать его std::shared_ptr, когда это будет уместно.

Опасности использования std::shared_ptr


У std::shared_ptr есть некоторые из проблем, которые имеет std::unique_ptr. Если std::shared_ptr не уничтожается должным образом (либо потому, что он был динамически выделен и не удален должным образом, либо потому, что он был частью объекта, который был динамически выделен и не удален), тогда ресурс, которым управляет std::shared_ptr, также не будет удален. С std::unique_ptr вам нужно беспокоиться об удалении лишь одного указателя. А с std::shared_ptr вам придется беспокоиться об удалении всех указателях, владеющих ресурсом. Если какой-либо из std::shared_ptr, владеющий ресурсом, не будет должным образом уничтожен, то и сам ресурс также не будет уничтожен.

std::shared_ptr и массивы

В C++14 и более ранних версиях С++, std::shared_ptr не имеет поддержки управления динамическими массивами и, соответственно, не должен использоваться с ними. Начиная с C++17, в std::share_ptr добавили поддержку динамических массивов. Однако, в C++17 «забыли» добавить эту поддержку в std::make_shared, поэтому эту функцию не следует использовать для создания std::shared_ptr, указывающего на динамические массивы. Возможно, эта проблема будет решена в C++20.

Итого


std::shared_ptr используется для управления сразу несколькими умными указателями одним динамически выделенным ресурсом. Ресурс уничтожается лишь в том случае, когда уничтожены все std::shared_ptr, указывающие на него.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (10 оценок, среднее: 4,90 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *