Зачем мне std:: переместить std:: shared_ptr?

Я просматривал исходный код Clang, и я нашел этот фрагмент:

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = std::move(Value);
}

Почему я хочу std::move a std::shared_ptr?

Есть ли какая-либо точка передачи права собственности на общий ресурс?

Почему бы мне просто не сделать это?

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = Value;
}

Ответ 1

Я думаю, что одна вещь, в которой другие ответы недостаточно подчеркивали, - это скорость.

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

Переместив shared_ptr вместо копирования, мы "украдим" счетчик ссылок на атомы и аннулируем другой shared_ptr. "кража" счетчик ссылок не является атомарным, и он в сотни раз быстрее, чем копирование shared_ptr (и приращение или уменьшение числа ссылок атома).

Обратите внимание, что этот метод используется исключительно для оптимизации. копирование его (как вы предположили) столь же тонко функционально.

Ответ 2

Используя move, вы не должны увеличивать и сразу же уменьшать количество акций. Это может сэкономить вам несколько дорогостоящих атомных операций на счет использования.

Ответ 3

Операции перемещения (например, конструктор перемещения) для std::shared_ptr являются дешевыми, поскольку они в основном являются "указателями кражи" (от источника к месту назначения, а точнее, весь блок управления государством "украден" от источника к месту назначения, включая информацию счетчика ссылок).

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

Итак, подробно проанализируйте динамику количества ссылок в этом случае:

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

Если вы передадите sp по значению, а затем возьмите копию внутри метода CompilerInstance::setInvocation, у вас есть:

  • При вводе метода параметр shared_ptr создается с копией: ref count atomic increment.
  • Внутри тела метода вы копируете параметр shared_ptr в элемент данных: ref count atomic increment.
  • При выходе из метода параметр shared_ptr разрушается: ref count atomic декремент.

У вас есть два атомных приращения и один атомный декремент, всего три операции atom.

Вместо этого, если вы передаете параметр shared_ptr по значению, а затем std::move внутри метода (как это сделано в коде Клана), вы имеете:

  • При вводе метода параметр shared_ptr создается с копией: ref count atomic increment.
  • Внутри тела метода вы std::move параметр shared_ptr в член данных: количество ссылок не изменяется! Вы просто крадете указатели/состояние: не задействованы дорогостоящие операции подсчета количества атомов.
  • При выходе из метода параметр shared_ptr разрушается; но с тех пор, как вы перешли на шаге 2, нечего разрушать, поскольку параметр shared_ptr больше не указывает на что-либо. Опять же, никакого атомного декремента не происходит в этом случае.

Нижняя строка: в этом случае вы получаете только один атомный приращение ref count, т.е. просто один атомный.
Как вы можете видеть, это намного лучше, чем два атомных приращения плюс один атомный декремент (всего три атомарные операции) для случая копирования.

Ответ 4

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

Ответ 5

В этой ситуации существует две причины использования std:: move. Большинство ответов затрагивали проблему скорости, но игнорировали важную проблему, показывающую четкость кода.

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

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