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

时间:2010-07-22 15:48:24

标签: c++ c++11 boost shared-ptr

当一个函数需要shared_ptr(来自boost或C ++ 11 STL)时,你传递了它吗?

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

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

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

请您说出您选择的理由或案例,为什么您认为无关紧要。

10 个答案:

答案 0 :(得分:191)

斯科特,安德烈和赫伯在Ask Us Anything会议期间C++ and Beyond 2011讨论并回答了这个问题。观看4分34秒on shared_ptr performance and correctness

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

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

GoingNative 2012会议Interactive Panel: Ask Us Anything!期间发生了此次讨论的重大更新,值得关注,尤其是来自22:50

答案 1 :(得分:71)

此处Herb Sutter's take

  

指南:除非将智能指针作为函数参数传递   你想使用或操纵智能指针本身,如   分享或转让所有权。

     

指南:表示某个功能将存储和共享a的所有权   使用by-value shared_ptr参数的堆对象。

     

指南:使用a   非const shared_ptr&amp;参数仅用于修改shared_ptr。用一个   const shared_ptr&amp;仅作为参数,如果您不确定是否或   不是你会复制并分享所有权;否则使用小部件*   相反(或者如果不是可空的话,小部件&amp;)。

答案 2 :(得分:58)

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

答案 3 :(得分:32)

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

答案 4 :(得分:19)

我运行了下面的代码,一次是fooshared_ptrconst&foo再次按shared_ptr按值。

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;
}

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

const shared_ptr&     - 10ms  
shared_ptr            - 281ms 

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

答案 5 :(得分:9)

从C ++ 11开始,你应该比你想象的更频繁地将它强加在const&amp; 之上。

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

如果您希望在某处复制,则通过复制更有意义,并且std ::在内部移动它,而不是通过const&amp;然后再复制它。这是因为你允许调用者在调用你的函数时依次调用std :: move the shared_ptr,从而节省了一组递增和递减操作。或不。也就是说,函数的调用者可以在调用函数之后决定他是否需要std :: shared_ptr,并且取决于是否移动。如果你通过const&amp;,这是不可能实现的,因此最好是按值进行。

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

例如,您应该

void enqueue(std::shared<T> t) m_internal_queue.enqueue(std::move(t));

void enqueue(std::shared<T> const& t) m_internal_queue.enqueue(t);

因为在这种情况下你总是在内部创建一个副本

答案 6 :(得分:6)

最近有一篇博客文章: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在https://stackoverflow.com/a/26197326/1930508中指出的那样,来自Herb Sutter的建议是:

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

但是您不确定有多少种情况?所以这是一种罕见的情况

答案 7 :(得分:2)

一个众所周知的问题是,按值传递shared_ptr会产生成本,应尽可能避免。

The cost of passing by shared_ptr

在大多数情况下,按引用传递shared_ptr,甚至通过const引用传递更好的时间。

cpp核心准则有一个用于传递shared_ptr的特定规则

R.34: Take a shared_ptr parameter to express that a function is part owner

void share(shared_ptr<widget>);            // share -- "will" retain refcount

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

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

答案 8 :(得分:1)

不知道原子增量和减量所在的shared_copy复制操作的时间成本,我遭受了更高的CPU使用率问题。我从未预料到原子增量和减量可能需要花费很多成本。

在我的测试结果之后,int32原子增量和减量比非原子增量和减量需要2或40倍。我在Windows 8.1的3GHz Core i7上得到了它。当没有发生争用时,前一个结果出现,后者发生争用的可能性很高。我记住原子操作最后是基于硬件的锁。锁是锁。争用发生时性能不佳。

遇到这种情况,我总是使用byref(const shared_ptr&amp;)而不是byval(shared_ptr)。

答案 9 :(得分:-4)

shared_ptr不够大,它的构造函数\析构函数也没有做足够的工作,因为副本有足够的开销来关注引用传递和复制性能传递。