我们应该通过引用还是通过值传递shared_ptr?

Dan*_*vil 253 c++ boost shared-ptr c++11

当一个函数采用shared_ptr(来自boost或C++ 11 STL)时,你传递它:

  • 通过const引用: void foo(const shared_ptr<T>& p)

  • 或按价值:void foo(shared_ptr<T> p)

我更喜欢第一种方法,因为我怀疑它会更快.但这真的值得吗还是还有其他问题吗?

您能否说出您选择的原因或案例,为什么您认为无关紧要.

mlo*_*kot 217

Scott,Andrei和Herb 在C++和2011年之后的Ask Us Anything会议期间讨论并回答了这个问题.观看4分34秒性能和正确性.shared_ptr

简而言之,除非目标是共享对象的所有权(例如,在不同数据结构之间或不同线程之间),否则没有理由按值传递.

除非,您可以按照上面链接的谈话视频中的Scott Meyers所解释的那样移动优化它,但这与您可以使用的C++的实际版本有关.

GoingNative 2012大会的交互式面板:向我们提出任何问题时,本次讨论的主要更新发生了!这值得关注,特别是从22:50开始.

  • 但是如图所示,通过值传递更便宜:http://stackoverflow.com/a/12002668/128384也不应该被考虑在内(至少对于构造函数参数等,其中shared_ptr将是成为班上的一员)? (5认同)
  • @Alex常见做法是在课后立即创建别名(typedef).对于你的例子:`class Value {...}; 使用ValuePtr = std :: shared_ptr <Value>;`然后你的函数变得更简单:`void函数(const ValuePtr&v1,const ValuePtr&v2,const ValuePtr&v3)`并且你获得了最大的性能.这就是你使用C++的原因,不是吗?:) (5认同)
  • 我仍然不明白除非子句:“除非目标是共享对象的所有权”——“shared_ptr”不总是这样吗?此外,值语义更加“自然”。通过引用传递总是需要理由,而不是相反。为什么我们应该通过引用传递? (5认同)
  • 我在派对上已经很晚了,但是我想要通过值传递shared_ptr的原因是它使代码更短更漂亮.认真.`Value*`简短易读,但它很糟糕,所以现在我的代码充满了`const shared_ptr <Value>&`而且它的可读性显着降低,而且......不那么整洁.曾经被认为是`空隙功能(值*V1,值*V2,值*V3)`现在`void函数(常量的shared_ptr <值>&V1,常量的shared_ptr <值>&V2,常量的shared_ptr <值>和v3 )`,人们对此好吗? (3认同)
  • @stijn是和否.您指出的问答是不完整的,除非它澄清了它引用的C++标准的版本.传播一般的永不/永远的规则是非常容易的,这些规则只是误导.除非读者花时间熟悉David Abrahams的文章和参考文献,或者将发布日期与当前的C++标准考虑在内.所以,鉴于发布时间,我和你指出的答案都是正确的. (2认同)
  • 通过查看 Herb Sutter 的[这篇文章](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/),这现在似乎已经过时了 (2认同)
  • @mloskot我确实看到了:...不要传递智能指针,除非您想使用/修改智能指针本身(就像任何对象一样)。最主要的是,按值传递/*/&amp; 仍然很好,仍然应该主要使用。只是我们现在有一些习惯用法来在函数签名中表达所有权转移,特别是按值传递 unique_ptr 意味着“下沉”,按值传递 Shared_ptr 意味着“将共享所有权”。所以有理由这样做 (2认同)
  • 编辑队列已满,这是 GoNative 2012 演讲的工作链接:https://learn.microsoft.com/en-us/events/goingnative-2012/interactive-panel-ask-us-anything (2认同)

小智 83

这是Herb Sutter的看法

准则:除非您想使用或操纵智能指针本身,例如共享或转移所有权,否则不要将智能指针作为函数参数传递.

准则:表示函数将使用by-value shared_ptr参数存储和共享堆对象的所有权.

指南:仅使用非const shared_ptr&参数来修改shared_ptr.仅当您不确定是否要复制并共享所有权时,才使用const shared_ptr&作为参数; 否则使用widget*(或者如果不是可空的,则使用小部件&).

  • 我认为这应该是今天接受的答案,因为第二条准则.它显然使当前接受的答案无效,即:没有理由通过价值. (4认同)
  • 感谢您与Sutter的链接.这是一篇很棒的文章.我在widget*上不同意他,如果C++ 14可用,则更喜欢可选的<widget&>.widget*与旧代码相比过于模糊. (3认同)
  • +1包含小部件*和小部件&作为可能性.只是详细说明,当函数没有检查/修改指针对象本身时,传递widget*或widget&可能是最好的选择.接口更通用,因为它不需要特定的指针类型,并且躲避了shared_ptr引用计数的性能问题. (3认同)
  • @Eponymous我认为不允许使用可选&lt;reference&gt;:https://www.fluencecpp.com/2018/10/05/pros-cons-optional-references/ (2认同)

Eva*_*ran 60

我会个人使用const参考.为了进行函数调用,不需要递增引用计数只是为了再次递减它.


Nik*_*sov 36

通过const引用传递,它更快.如果您需要存储它,比如在某个容器中,请参考.计数将通过复制操作自动增加.

  • Downvote因为其意见而没有任何数字支持它. (3认同)
  • @kwesolowski 答案提供了为什么 const 引用更快的分析原因(即没有不必要的引用计数递增/递减)。它是基准测试的替代方案。 (2认同)

tcb*_*tcb 21

我运行了下面的代码,一次foo采用了shared_ptrby const&并再次foo采用了shared_ptrby值.

void foo(const std::shared_ptr<int>& p)
{
    static int x = 0;
    *p = ++x;
}

int main()
{
    auto p = std::make_shared<int>();
    auto start = clock();
    for (int i = 0; i < 10000000; ++i)
    {
        foo(p);
    }    
    std::cout << "Took " << clock() - start << " ms" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

在我的intel core 2 quad(2.4GHz)处理器上使用VS2015,x86发布版

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 
Run Code Online (Sandbox Code Playgroud)

按值复制版本的速度要慢一个数量级.
如果从当前线程同步调用函数,则更喜欢该const&版本.

  • 优化没有多大帮助.问题是对副本上的引用计数的锁争用. (2认同)

Coo*_*kie 14

从C++ 11开始,你应该通过const来获取它,而不是你想象的那样.

如果您正在使用std :: shared_ptr(而不是基础类型T),那么您正在这样做,因为您想要对它执行某些操作.

如果你想将它复制到某个地方,那么通过复制更有意义,并且std ::在内部移动它,而不是通过const接受它然后再复制它.这是因为你允许调用者在调用你的函数时依次调用std :: move the shared_ptr,从而节省了一组递增和递减操作.或不.也就是说,函数的调用者可以在调用函数之后决定他是否需要std :: shared_ptr,并且取决于是否移动.如果你传递const&,这是不可能实现的,因此最好是通过值来获取它.

当然,如果调用者都需要他的shared_ptr更长时间(因此不能std :: move它)并且你不想在函数中创建一个普通的副本(假设你想要一个弱指针,或者你只是有时想要复制它,取决于某些条件),然后const&可能仍然是更可取的.

例如,你应该这样做

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));
Run Code Online (Sandbox Code Playgroud)

过度

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);
Run Code Online (Sandbox Code Playgroud)

因为在这种情况下,您始终在内部创建副本


Fla*_*ire 12

最近有一篇博文:https : //medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce

所以这个问题的答案是:做(几乎)从不经过const shared_ptr<T>&
只需传递基础类即可。

基本上唯一合理的参数类型是:

  • shared_ptr<T> - 修改并取得所有权
  • shared_ptr<const T> - 不要修改,拥有所有权
  • T& - 修改,无所有权
  • const T& - 不要修改,没有所有权
  • T - 不要修改,没有所有权,复制便宜

正如@accel 在/sf/answers/1833812851/ 中指出的,Herb Sutter 的建议是:

仅当您不确定是否要复制并共享所有权时,才使用 const shared_ptr& 作为参数

但在多少情况下你不确定?所以这是一种罕见的情况

  • IMO,这是正确但最简洁的答案之一。 (4认同)

art*_*rtm 6

众所周知,按值传递 shared_ptr 会产生成本,因此应尽可能避免。

传递shared_ptr的成本

大多数时候通过引用传递shared_ptr,甚至通过const引用传递更好,就可以了。

cpp核心指南对于传递shared_ptr有一个特定的规则

R.34:采用shared_ptr参数来表示函数是部分所有者

void share(shared_ptr<widget>);            // share -- "will" retain refcount
Run Code Online (Sandbox Code Playgroud)

确实需要按值传递shared_ptr 的一个示例是,当调用者将共享对象传递给异步被调用者时,即调用者在被调用者完成其工作之前超出范围。被调用者必须通过按值获取 share_ptr 来“延长”共享对象的生命周期。在这种情况下,传递对shared_ptr的引用是行不通的。

将共享对象传递给工作线程也是如此。