Std:: shared_ptr: reset() по сравнению с назначением
Это основной вопрос, но я не нашел предыдущего сообщения об этом. Название следующего вопроса звучит так, как будто это может быть тот же вопрос, что и мой, но сам вопрос не соответствует названию: лучше использовать shared_ptr.reset или operator =?
Я запутался в цели функции reset()
члена std::shared_ptr
: что она вносит в дополнение к оператору присваивания?
Чтобы быть конкретным, учитывая определение:
auto p = std::make_shared<int>(1);
Если две строки эквивалентны в обоих случаях, то в чем цель reset()
?
EDIT: Позвольте мне перефразировать вопрос, чтобы лучше подчеркнуть его точку. Возникает вопрос: существует ли случай, когда reset()
позволяет нам достичь чего-то, что не так легко достижимо без него?
Ответы
Ответ 1
При использовании reset()
параметр, переданный в reset, не должен быть управляемым объектом (и не может быть); тогда как с =
правая сторона должна быть управляемым объектом.
Итак, эти две строки дают один и тот же конечный результат:
p = std::make_shared<int>(5); // assign to a newly created shared pointer
p.reset(new int(5)); // take control of a newly created pointer
Но мы не можем:
p = new int(5); // compiler error no suitable overload
p.reset(std::make_shared<int>(5).get()); // uh oh undefined behavior
Без reset()
вы не сможете переназначить общий указатель на другой необработанный указатель без создания общего указателя и его назначения. Без =
вы не сможете сделать общую точку указателя на другой общий указатель.
Ответ 2
В reset
возможно избегать распределения динамической памяти в определенных случаях. Рассмотрим код
std::shared_ptr<int> p{new int{}}; // 1
p.reset(new int{}); // 2
В строке 1 есть 2 распределения динамической памяти, один для объекта int
и второй для блока управления shared_ptr
, который будет отслеживать количество сильных/слабых ссылок на управляемый объект.
В строке 2 снова появляется динамическое выделение памяти для нового объекта int
. В теле reset
shared_ptr
будет определено, что нет других сильных ссылок на ранее управляемый int
, поэтому он должен delete
его. Так как нет никаких слабых ссылок, он также может освободить контрольный блок, но в этом случае было бы разумно, чтобы реализация повторно использовала один и тот же блок управления, потому что в противном случае ему пришлось бы выделять новый.
Вышеуказанное поведение было бы невозможным, если бы вам всегда приходилось использовать назначение.
std::shared_ptr<int> p{new int{}}; // 1
p = std::shared_ptr<int>{new int{}}; // 2
В этом случае второй вызов конструктора shared_ptr
в строке 2 уже выделил контрольный блок, поэтому p
должен будет освободить свой существующий блок управления.
Ответ 3
Я не буду включать в себя обоснование вашего первого вопроса о различии между конструкцией через make_shared
или указателем, так как эта разница выделяется в нескольких разных местах, включая этот отличный вопрос.
Однако я считаю конструктивным различать использование reset
и operator=
. Первый отказывается от права собственности на ресурс, управляемый shared_ptr
, либо уничтожая его, если shared_ptr
оказался единственным владельцем, либо уменьшив счетчик ссылок. Последнее подразумевает совместное владение с другим shared_ptr
(если вы не перемещаете конструкцию).
Как я уже упоминал в комментариях, важно, чтобы указатель, переданный в reset
, не принадлежал другому общему или уникальному указателю, поскольку он мог бы привести к поведению undefined при уничтожении двух независимых менеджеров - они оба будет пытаться delete
ресурса.
Одним из вариантов использования reset
может быть ленивая инициализация общего ресурса. Вы хотите, чтобы shared_ptr
управлял некоторым ресурсом, например, памятью, если вам это действительно нужно. Выполнение прямого распределения, например:
std::shared_ptr<resource> shared_resource(new resource(/*construct a resource*/));
может быть расточительным, если его никогда не нужно. Для этого с ленивой инициализацией может применяться следующее:
std::shared_ptr<resource> shared_resource;
void use_resource()
{
if(!shared_resource)
{
shared_resource.reset(new resource(...));
}
shared_resource->do_foo();
}
Использование reset
в этом случае является более кратким, чем выполнение swap
или присвоение временному shared_ptr
.
Ответ 4
reset()
изменяет управляемый объект существующего shared_ptr
.
p = std:: shared_ptr (новый int (5)); и p.reset(новый int (5));
Первый включает в себя создание нового shared_ptr
и перенос его в переменную. Последний не создает новый объект, он просто меняет основной указатель в управляемом shared_ptr
.
Другими словами, эти два предназначены для использования в разных случаях. Назначение выполняется, если у вас есть shared_ptr
и reset
, если у вас есть необработанный указатель.
Еще одна вещь, о которой нужно помнить, заключается в том, что shared_ptr
был доступен в boost до того, как было назначено перемещение и сильно повлияло на последнюю версию. Без назначения перемещения, способного изменить shared_ptr
, не делая копию, полезно, так как это избавляет вас от учета дополнительного объекта.