假设我有一个类,其中包含一个智能指针作为其成员变量:
class B;
class A {
public:
A(const std::shared_ptr<B>& b) : b_(b) {} // option1: passing by reference
A(std::shared_ptr<B> b) : b_(b) {} // option2: passing by value
std::shared_ptr<B> b_;
};
A的构造函数有两种选择:通过智能指针构造和通过智能指针的引用构造。
这两种方法各有什么优缺点?
复制智能指针是否浪费?
最好的选择是选项#3:
A(std::shared_pointer<B> b) : b_(std::move(b)) {} // option3: passing by value and move
与
option1
不同,当 shared_ptr
是根据传递给 A
的构造函数的纯右值构造时,它不会执行不必要的复制。与 option2
不同,它不会在内部执行不必要的复制。
费用为:
shared_ptr
来说更有效,因为它避免了在复制和销毁源时需要操作引用计数的方式)A(std::move(callers_ptr))
)时,它会移动构造两次(再次避免任何 refcnt 操作),但同样,没有副本所以成本是:
shared_ptr
)为了比较,每个场景中的选项 #1 需要:
shared_ptr
)const
引用生命周期扩展的 C++ 规则,不是 100%;无论哪种方式,考虑到 中涉及的原子,一份副本都比两次移动更糟糕shared_ptr
)std::move
将选项 #2 更改为选项 #3 是一个纯粹的胜利。
所以,是的,如果调用者总是保留自己对
shared_ptr
的所有权,同时也给予A
自己的所有权,永远不会将其所有权转移给新A
。但您保存的每一步都只是几个非原子指针分配;无论哪种方式,您都可以将指针从源复制到目标,通过移动将
NULL
移出源,而保存副本意味着您可以避免通过指向非本地控制块的指针原子地增加引用计数(可能比本地非原子指针分配昂贵一个数量级)。
注意:有一个选项#4,正如 Nathan 在评论中提到的那样,严格来说性能更高,使用完美转发构造函数在每种情况下跳过移动操作。缺点是代码变得更加复杂,并且如果用例更加复杂(不仅仅是单个简单的
shared_ptr
成员),那么您可能会遇到与(通常不是 noexcept
noexcept
并且避免需要处理中间初始化异常的可能性。
我认为 Herb Sutter 的这个答案
足够清楚了但是指南很简单——不要传递智能指针,除非你想使用/修改智能指针本身(就像任何对象一样)。最主要的是,按值传递/*/& 仍然很好,仍然应该主要使用。只是我们现在有一些习惯用语来表达函数签名中的所有权转移,特别是按值传递 unique_ptr 意味着“下沉”,按值传递共享_ptr 意味着“将共享所有权”。差不多就这些了。