nic*_*ick 6 c++ smart-pointers c++11
假设我有一个类,其中包含一个智能指针作为其成员变量:
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_;
};
Run Code Online (Sandbox Code Playgroud)
我对A的构造函数有两种选择:通过智能指针构造和通过智能指针的引用构造。
这两种方法各有什么优缺点?
复制智能指针是浪费吗?
最好的选择是选项#3:
A(std::shared_pointer<B> b) : b_(std::move(b)) {} // option3: passing by value and move
Run Code Online (Sandbox Code Playgroud)
与 不同的是,当 是从传递给 的构造函数的纯右值构造option1时,它不会执行不必要的复制。与 不同的是,它不会在内部执行不必要的复制。shared_ptrAoption2
费用为:
shared_ptr,因为它避免了在复制和销毁源时需要按照必须的方式操作引用计数)A(std::move(callers_ptr)))时,它会移动构造两次(再次避免任何 refcnt 操作),但同样,没有副本所以成本是:
shared_ptr)为了进行比较,每个场景中的选项 #1 都需要:
shared_ptr)const在这种情况下,对于引用生命周期扩展的 C++ 规则不是 100%;无论哪种方式,考虑到 s 中涉及的原子,一个副本都比两个移动更糟糕shared_ptr)每个场景中的选项 #2 与选项 #3 完全相同,但每个场景中的一个移动变成了副本(因此选项 #2 在每个场景中客观上更差);添加std::move将选项 #2 更改为选项 #3 是一个纯粹的胜利。
所以,是的,如果调用者始终保留自己的所有权,同时也给予其自己的所有权,而不将其所有权转移给新的,那么选项#1可能会稍微更有效。但您保存的每一步都只是几个非原子指针分配;无论哪种方式,您都可以将指针从源复制到目标,通过 move 将 ing 添加到源之外,而保存副本意味着您可以避免通过指向非本地控制块的指针原子地增加引用计数(可能是比本地非原子指针分配昂贵一个数量级)。shared_ptrAANULL
注意:正如 Nathan 在评论中提到的,有一个选项#4,它的性能更高,在每种情况下使用完美转发构造函数来跳过移动操作。缺点是代码变得更加复杂,并且如果用例更加复杂(不仅仅是单个简单的shared_ptr成员),那么您可能会遇到与noexcept构造函数中发生的(通常不是)构造/复制操作相关的潜在问题,而不是在构造函数中调用方,因此仅部分构造对象时发生异常的风险会增加。只要所有非移动操作发生在构造函数外部(意味着它们是针对参数完成的),构造函数本身通常就可以避免noexcept需要处理中间初始化异常的可能性。