Подтвердить что ты не робот

Std:: shared_ptr: reset() по сравнению с назначением

Это основной вопрос, но я не нашел предыдущего сообщения об этом. Название следующего вопроса звучит так, как будто это может быть тот же вопрос, что и мой, но сам вопрос не соответствует названию: лучше использовать shared_ptr.reset или operator =?

Я запутался в цели функции reset() члена std::shared_ptr: что она вносит в дополнение к оператору присваивания?

Чтобы быть конкретным, учитывая определение:

auto p = std::make_shared<int>(1);
  • Являются ли следующие две строки эквивалентными:

    p = std::make_shared<int>(5);
    p.reset(new int(5));
    
  • Как насчет этих:

    p = nullptr;
    p.reset();
    

Если две строки эквивалентны в обоих случаях, то в чем цель reset()?


EDIT: Позвольте мне перефразировать вопрос, чтобы лучше подчеркнуть его точку. Возникает вопрос: существует ли случай, когда reset() позволяет нам достичь чего-то, что не так легко достижимо без него?

4b9b3361

Ответ 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, не делая копию, полезно, так как это избавляет вас от учета дополнительного объекта.