C++ 为什么我要 std::move 一个 std::shared_ptr ?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41871115/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:28:18  来源:igfitidea点击:

Why would I std::move an std::shared_ptr?

c++c++11shared-ptrsmart-pointersmove-semantics

提问by sdgfsdh

I have been looking through the Clang source codeand I found this snippet:

我一直在查看Clang 源代码,我发现了这个片段:

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

Why would I want to std::movean std::shared_ptr?

为什么我想要std::move一个std::shared_ptr

Is there any point transferring ownership on a shared resource?

转让共享资源的所有权有什么意义吗?

Why wouldn't I just do this instead?

为什么我不这样做呢?

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

采纳答案by David Haim

I think that the one thing the other answers did not emphasize enough is the point of speed.

我认为其他答案没有足够强调的一件事是速度

std::shared_ptrreference count is atomic. increasing or decreasing the reference count requires atomicincrement or decrement. This is hundred times slowerthan non-atomicincrement/decrement, not to mention that if we increment and decrement the same counter we wind up with the exact number, wasting a ton of time and resources in the process.

std::shared_ptr引用计数是原子的。增加或减少引用计数需要原子递增或递减。这比非原子递增/递减一百倍,更不用说如果我们递增和递减同一个计数器,我们会得到确切的数字,在这个过程中浪费了大量的时间和资源。

By moving the shared_ptrinstead of copying it, we "steal" the atomicreference count and we nullify the other shared_ptr. "stealing" the reference count is not atomic, and it is hundred times faster than copying the shared_ptr(and causing atomicreference increment or decrement).

通过移动shared_ptr而不是复制它,我们“窃取”了原子引用计数并将另一个shared_ptr. “窃取”引用计数不是原子的,它比复制shared_ptr(并导致原子引用增加或减少)快一百倍。

Do note that this technique is used purely for optimization. copying it (as you suggested) is just as fine functionality-wise.

请注意,此技术仅用于优化。复制它(如您所建议的)在功能方面同样出色。

回答by Bo Persson

By using moveyou avoid increasing, and then immediately decreasing, the number of shares. That might save you some expensive atomic operations on the use count.

通过使用,move您可以避免增加然后立即减少共享数量。这可能会在使用计数上为您节省一些昂贵的原子操作。

回答by Mr.C64

Moveoperations (like move constructor) for std::shared_ptrare cheap, as they basically are "stealing pointers"(from source to destination; to be more precise, the whole state control block is "stolen" from source to destination, including the reference count information).

移动操作(如移动构造函数)std::shared_ptr便宜,因为它们基本上是“窃取指针”(从源到目标;更准确地说,整个状态控制块从源到目标被“窃取”,包括引用计数信息) .

Instead copyoperations on std::shared_ptrinvoke atomicreference count increase (i.e. not just ++RefCounton an integer RefCountdata member, but e.g. calling InterlockedIncrementon Windows), which is more expensivethan just stealing pointers/state.

相反,在调用原子引用计数增加时进行复制操作(即不仅仅是在整数数据成员上,而是例如在 Windows 上调用),这比仅仅窃取指针/状态更昂贵std::shared_ptr++RefCountRefCountInterlockedIncrement

So, analyzing the ref count dynamics of this case in details:

因此,详细分析此案例的引用计数动态:

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

If you pass spby value and then take a copyinside the CompilerInstance::setInvocationmethod, you have:

如果您sp按值传递,然后在方法中复制一份CompilerInstance::setInvocation,您将获得:

  1. When entering the method, the shared_ptrparameter is copy constructed: ref count atomicincrement.
  2. Inside the method's body, you copythe shared_ptrparameter into the data member: ref count atomicincrement.
  3. When exiting the method, the shared_ptrparameter is destructed: ref count atomicdecrement.
  1. 进入方法时,shared_ptr参数被复制构造: ref count atomic increment
  2. 在方法的身体,你复制shared_ptr参数为数据成员:引用计数原子增量
  3. 退出方法时,shared_ptr参数被破坏: ref count atomic decrement

You have two atomic increments and one atomic decrement, for a total of threeatomicoperations.

您有两个原子增量和一个原子减量,总共三个原子操作。

Instead, if you pass the shared_ptrparameter by value and then std::moveinside the method (as properly done in Clang's code), you have:

相反,如果您shared_ptr按值传递参数,然后std::move在方法内部(在 Clang 的代码中正确完成),您将:

  1. When entering the method, the shared_ptrparameter is copy constructed: ref count atomicincrement.
  2. Inside the method's body, you std::movethe shared_ptrparameter into the data member: ref count does notchange! You are just stealing pointers/state: no expensive atomic ref count operations are involved.
  3. When exiting the method, the shared_ptrparameter is destructed; but since you moved in step 2, there's nothing to destruct, as the shared_ptrparameter is not pointing to anything anymore. Again, no atomic decrement happens in this case.
  1. 进入方法时,shared_ptr参数被复制构造: ref count atomic increment
  2. 在方法的身体,你std::moveshared_ptr参数为数据成员:引用计数并没有改变!您只是在窃取指针/状态:不涉及昂贵的原子引用计数操作。
  3. 退出方法时,shared_ptr参数被销毁;但是由于您在第 2 步中移动,因此没有什么可破坏的,因为shared_ptr参数不再指向任何内容。同样,在这种情况下不会发生原子递减。

Bottom line: in this case you get just oneref count atomic increment, i.e. just one atomicoperation.
As you can see, this is much betterthan twoatomic increments plus oneatomic decrement (for a total of threeatomic operations) for the copy case.

底线:在这种情况下,您只会获得一个引用计数原子增量,即只有一个原子操作。
如您所见,对于复制情况,这比两个原子增量加一个原子减量(总共三个原子操作)要好得多

回答by SingerOfTheFall

Copying a shared_ptrinvolves copying its internal state object pointer and changing the reference count. Moving it only involves swapping pointers to the internal reference counter, and the owned object, so it's faster.

复制 ashared_ptr涉及复制其内部状态对象指针并更改引用计数。移动它只涉及交换指向内部引用计数器的指针和拥有的对象,所以它更快。

回答by Stephen C. Steel

There are two reasons for using std::move in this situation. Most responses addressed the issue of speed, but ignored the important issue of showing the code's intent more clearly.

在这种情况下使用 std::move 有两个原因。大多数响应都解决了速度问题,但忽略了更清楚地显示代码意图的重要问题。

For a std::shared_ptr, std::move unambiguously denotes a transfer of ownership of the pointee, while a simple copy operation adds an additional owner. Of course, if the original owner subsequently relinquishes their ownership (such as by allowing their std::shared_ptr to be destroyed), then a transfer of ownership has been accomplished.

对于 std::shared_ptr, std::move 明确表示指针对象所有权的转移,而简单的复制操作添加了一个额外的所有者。当然,如果原始所有者随后放弃了他们的所有权(例如允许他们的 std::shared_ptr 被销毁),那么所有权的转移已经完成。

When you transfer ownership with std::move, it's obvious what is happening. If you use a normal copy, it isn't obvious that the intended operation is a transfer until you verify that the original owner immediately relinquishes ownership. As a bonus, a more efficient implementation is possible, since an atomic transfer of ownership can avoid the temporary state where the number of owners has increased by one (and the attendant changes in reference counts).

当您使用 std::move 转移所有权时,很明显发生了什么。如果您使用普通副本,则在您确认原始所有者立即放弃所有权之前,预期操作是转移并不明显。作为奖励,更有效的实现是可能的,因为所有权的原子转移可以避免所有者数量增加一的临时状态(以及随之而来的引用计数变化)。

回答by A. K.

At least with libstdc++ you should get the same performance with move and assignment because operator=calls std::moveon the incoming pointer. See: https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384

至少在 libstdc++ 中,您应该在移动和赋值方面获得相同的性能,因为operator=调用std::move传入的指针。参见:https: //github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/shared_ptr.h#L384