C++ 为什么我要移动std::shared\u ptr?
我一直在浏览,发现了以下片段:C++ 为什么我要移动std::shared\u ptr?,c++,c++11,shared-ptr,smart-pointers,move-semantics,C++,C++11,Shared Ptr,Smart Pointers,Move Semantics,我一直在浏览,发现了以下片段: void CompilerInstance::setInvocation( std::shared_ptr<CompilerInvocation> Value) { Invocation = std::move(Value); } 通过使用move可以避免增加然后立即减少股票数量。这可能会在使用计数上为您节省一些昂贵的原子操作 复制共享\u ptr涉及复制其内部状态对象指针并更改引用计数。移动它只需要交换指向内部引用计数器和所属对象的指针
void CompilerInstance::setInvocation(
std::shared_ptr<CompilerInvocation> Value) {
Invocation = std::move(Value);
}
通过使用
move
可以避免增加然后立即减少股票数量。这可能会在使用计数上为您节省一些昂贵的原子操作 复制共享\u ptr
涉及复制其内部状态对象指针并更改引用计数。移动它只需要交换指向内部引用计数器和所属对象的指针,因此速度更快。std::shared_ptr
的移动操作(如移动构造函数)很便宜,因为它们基本上是“偷指针”(从源到目标;更准确地说,整个状态控制块都是“偷来的”从源到目标,包括引用计数信息)
相反,对std::shared_ptr
调用原子引用计数增加的复制操作(即不仅对整数RefCount
数据成员执行+RefCount
,而且在Windows上调用interlockedCrement
),这比仅仅窃取指针/状态更昂贵
因此,详细分析该案例的参考计数动态:
// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);
//共享\u ptr sp;
compilerInstance.setInvocation(sp);
如果您按值传递sp,然后在CompilerInstance::setInvocation
方法中进行复制,则您有:
shared_ptr
参数为copy-constructed:ref-count-atomicincrement共享的ptr
参数复制到数据成员:ref count atomicincrement中shared_ptr
参数被销毁:ref count atomic递减shared_ptr
参数,然后在方法内部传递std::move
(在Clang的代码中正确完成),则您有:
shared_ptr
参数为copy-constructed:ref-count-atomicincrementstd::将shared_ptr
参数移动到数据成员中:ref count不会更改!您只是在窃取指针/状态:不涉及昂贵的原子引用计数操作
shared_ptr
参数被销毁;但是,由于您在步骤2中移动了,所以没有什么可破坏的,因为shared\u ptr
参数不再指向任何东西。同样,在这种情况下不会发生原子衰减如您所见,对于复制情况,这比两个原子增量加一个原子减量(总共三个原子操作)要好得多。我认为其他答案没有充分强调的一点是速度
std::shared_ptr
引用计数是原子的。增加或减少参考计数需要原子增量或减量。这比非原子递增/递减慢一百倍,更不用说如果我们递增或递减同一个计数器,我们得到的是精确的数字,在这个过程中浪费了大量的时间和资源
通过移动共享的\u ptr
而不是复制它,我们“窃取”原子引用计数,并使另一个共享的\u ptr
无效。“窃取”引用计数不是原子的,它比复制共享\u ptr
快一百倍(并导致原子引用增量或减量)
请注意,此技术纯粹用于优化。复制它(正如您所建议的那样)在功能方面也很好。在这种情况下使用std::move有两个原因。大多数回答都提到了速度问题,但忽略了更清楚地显示代码意图这一重要问题 对于std::shared_ptr,std::move明确表示指针对象的所有权转移,而简单的复制操作会添加额外的所有者。当然,如果原始所有者随后放弃其所有权(例如允许销毁其std::shared_ptr),则所有权转移已经完成
当您使用std::move转移所有权时,很明显发生了什么。如果使用普通副本,除非验证原始所有者立即放弃所有权,否则无法确定预期操作是否为传输。作为奖励,更高效的实现是可能的,因为原子所有权转移可以避免所有者数量增加1(以及随之而来的引用计数变化)的临时状态.至少使用libstdc++时,移动和赋值的性能应该相同,因为
操作符=
调用传入指针上的std::move
。请参阅:另一个值得注意的是:为什么它们不直接通过const引用传递,并避免整个std::move内容?因为“按值传递”还允许您直接传入原始指针,并且将只创建一个共享\u ptr。@Joseph,因为您无法移动常量reference@JosephIreland因为如果您将其称为compilerInstance.setInvocation(std::move(sp))代码>则不会有增量。您可以通过添加一个重载来获得相同的行为,该重载需要一个共享\u ptr&&
,但为什么在不需要复制的时候复制呢?@Brunofereira我在回答我自己的问题。您不需要移动它,因为它是引用,只需复制它即可。仍然只有一份而不是两份。他们不这样做的原因是因为它会不必要地复制新构建的共享资源,例如。
// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);