虽然我花了一段时间才习惯它,但我现在养成了让我的函数通过左值引用const
而不是值来获取共享指针参数的习惯(除非我需要修改原始参数,当然,在这种情况下,我通过左值引用非const
):
void foo(std::shared_ptr<widget> const& pWidget)
// ^^^^^^
{
// work with pWidget...
}
这样做的好处是可以避免不必要的共享指针副本,这意味着线程安全地增加了引用计数,并可能导致不必要的开销。
现在我一直想知道对于检索共享指针采用一种有点对称的习惯是否合理,这些共享指针是通过函数的值返回的,比如下面的代码片段末尾:
struct X
{
// ...
std::shared_ptr<Widget> bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> const& pWidget = x.bar();
// ^^^^^^
采用这种编码习惯是否有任何陷阱?一般来说,我是否应该更喜欢将返回的共享指针分配给另一个共享指针对象而不是将其绑定到引用?
答案 0 :(得分:6)
这只是重写旧的问题,即捕获对临时的const引用是否比创建副本更有效。简单的答案是它不是。在该行:
// std::shared_ptr<Widget> bar();
std::shared_ptr<Widget> const & pWidget = bar();
编译器需要创建一个本地未命名的变量(不是临时的),通过调用bar()
来初始化它,然后将引用绑定到它:
std::shared_ptr<Widget> __tmp = bar();
std::shared_ptr<Widget> const & pWidget = __tmp;
在大多数情况下,它将避免创建引用,并在函数的其余部分中仅对原始对象进行别名,但在一天结束时,变量是否被称为pWidget
或__tmp
和别名不会给任何好处。
相反,对于休闲读者来说,bar()
可能看起来不会创建一个对象,但会产生对已存在的std::shared_ptr<Widget>
的引用,因此维护者必须找到bar()
定义{1}}以了解是否可以在此函数范围之外更改pWidget
。
通过绑定到const引用的生命周期扩展是语言中的一个奇怪的功能,它几乎没有实际用途(即当引用是基础时你并不十分关心返回的确切派生类型值,即ScopedGuard
)。
答案 1 :(得分:3)
您可以向后优化:
struct X
{
// ...
std::shared_ptr<Widget> const& bar() const
{
// ...
return pWidget;
}
// ...
std::shared_ptr<Widget> pWidget;
};
// ...
// X x;
std::share_ptr<Widget> pWidget = x.bar();
当bar
返回成员变量时,它必须在您的版本中复制shared_ptr
。如果通过引用返回成员变量,则可以避免复制。
这在您的原始版本和上面显示的版本中都不重要,但是如果您致电则会出现:
x.bar()->baz()
在您的版本中,将创建一个新的shared_ptr
,然后调用baz。
在我的版本中,baz直接在shared_ptr
的成员副本上调用,并且避免了原子参考增量/减量。
当然,shared_ptr
复制构造函数(原子增量)的成本非常小,除了性能最敏感的应用程序之外,其他所有成本都没有。如果您正在编写性能敏感的应用程序,那么更好的选择是使用内存池架构手动管理内存,然后(小心地)使用原始指针。
答案 2 :(得分:2)
在David Rodríguez - dribeas said之上添加,即绑定到const
引用不会使您无法进行复制,并且计数器仍会递增,以下代码说明了这一点点:
#include <memory>
#include <cassert>
struct X {
std::shared_ptr<int> p;
X() : p{new int} {}
std::shared_ptr<int> bar() { return p; }
};
int main() {
X x;
assert(x.p.use_count() == 1);
std::shared_ptr<int> const & p = x.bar();
assert(x.p.use_count() == 2);
return 0;
}