Я просматривал исходный код Clang и нашел этот фрагмент:
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
Зачем мне std::move
это std::shared_ptr
?
Есть ли смысл передавать право собственности на общий ресурс?
Почему бы мне просто не сделать это вместо этого?
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = Value;
}
При использовании
move
вы избегаете увеличения, а затем сразу же уменьшения количества акций. Это может сэкономить вам некоторые дорогостоящие атомарные операции по счету использования.источник
Операции перемещения (например, конструктор перемещения) для
std::shared_ptr
являются дешевыми , поскольку они в основном являются «крадящими указателями» (от источника к месту назначения; точнее, весь блок управления состоянием «украден» от источника к месту назначения, включая информацию о количестве ссылок) ,Вместо этого операции копирования при
std::shared_ptr
вызове увеличивают атомный счетчик ссылок (т.е. не только++RefCount
для целочисленногоRefCount
члена данных, но, например, при вызовеInterlockedIncrement
в Windows), что дороже, чем просто кража указателей / состояний.Итак, детально анализируя динамику подсчета ссылок в этом случае:
Если вы передаете
sp
по значению, а затем берете копию внутриCompilerInstance::setInvocation
метода, у вас есть:shared_ptr
параметр создается с помощью копии: ref count атомарный инкремент .shared_ptr
параметр в элементе данных: ЗАДАНИЕ рассчитывать атомное приращение .shared_ptr
параметр разрушается: ref count атомарный декремент .У вас есть два атомных приращения и один атомарный декремент, всего три атомарных операции.
Вместо этого, если вы передадите
shared_ptr
параметр по значению, а затемstd::move
внутри метода (как это правильно сделано в коде Кланга), вы получите:shared_ptr
параметр создается с помощью копии: ref count атомарный инкремент .std::move
shared_ptr
shared_ptr
параметр уничтожается; но поскольку вы перешли на шаге 2, уничтожать нечего, так какshared_ptr
параметр больше ни на что не указывает. Опять же, в этом случае не происходит атомного декремента.Итог: в этом случае вы получаете только один атомный инкремент подсчета ссылок, т.е. только одну атомарную операцию.
Как вы можете видеть, это намного лучше, чем два атомарных приращения плюс один атомарный декремент (всего три атомарных операции) для случая копирования.
источник
compilerInstance.setInvocation(std::move(sp));
то не будет никакого прироста . Вы можете получить то же поведение, добавив перегрузку, которая требует,shared_ptr<>&&
но зачем дублировать, когда вам не нужно.setInvocation(new CompilerInvocation)
или как упоминалось с храповикомsetInvocation(std::move(sp))
. Извините, если мой первый комментарий был неясным, я действительно опубликовал его случайно, до того как закончил писать, и решил просто оставить егоКопирование
shared_ptr
включает в себя копирование его внутреннего указателя объекта состояния и изменение счетчика ссылок. Его перемещение требует только замены указателей на внутренний счетчик ссылок и принадлежащий объект, так что это быстрее.источник
В этой ситуации есть две причины использования std :: move. Большинство ответов касались проблемы скорости, но игнорировали важную проблему более четкого представления намерений кода.
Для std :: shared_ptr std :: move однозначно обозначает передачу права владения pointee, тогда как простая операция копирования добавляет дополнительного владельца. Конечно, если первоначальный владелец впоследствии отказывается от своего права собственности (например, разрешив уничтожить его std :: shared_ptr), тогда передача права собственности была завершена.
Когда вы передаете право собственности с помощью std :: move, становится очевидным, что происходит. Если вы используете обычную копию, не очевидно, что предполагаемая операция является передачей, пока вы не убедитесь, что первоначальный владелец немедленно отказывается от владения. В качестве бонуса возможна более эффективная реализация, поскольку атомарная передача прав собственности может избежать временного состояния, когда количество владельцев увеличилось на единицу (и сопутствующие изменения в подсчете ссылок).
источник
По крайней мере, с libstdc ++ вы должны получить одинаковую производительность с перемещением и назначением, поскольку
operator=
вызываетstd::move
входящий указатель. Смотрите: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384источник