2017-01-26 3 views
81

Я смотрел через Clang source code и я нашел этот фрагмент кода:Зачем мне std :: move std :: shared_ptr?

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

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

Есть ли смысл передавать права собственности на общий ресурс?

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

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

ответ

47

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

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

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

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

91

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

+1

Это не преждевременная оптимизация? – YSC

+10

@YSC нет, если кто-то положил его там, фактически протестировал его. – OrangeDog

+0

@OrangeDog, конечно, глупо меня. – YSC

16

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

48

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

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

Таким образом, анализируя динамику подсчета ссылок этого дела в деталях:

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

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

  1. При вводе метода параметр shared_ptr является копией: ref count atomicПриращение.
  2. Внутри тела метода, вы скопировать параметр shared_ptr в элемент данных: исх счета атомноеприращение.
  3. При выходе из метода разрушается параметр shared_ptr: ref count атомныйдекрет.

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

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

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

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

+0

Также стоит отметить: почему бы им просто не перейти по ссылке const и избежать всего std :: move stuff? Поскольку pass-by-value также позволяет вам напрямую передать исходный указатель, и будет создан только один shared_ptr. –

+0

@JosephIreland Поскольку вы не можете переместить константу-ссылку –

+1

@JosephIreland, потому что если вы называете ее 'compilerInstance.setInvocation (std :: move (sp)),' тогда не будет никакого ** приращения **. Вы можете получить такое же поведение, добавив перегрузку, которая принимает 'shared_ptr <> &&', но почему дублировать, когда вам это не нужно. –

10

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

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

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

Смежные вопросы