当一个函数需要shared_ptr
(来自boost或C ++ 11 STL)时,你传递了它吗?
通过const引用:void foo(const shared_ptr<T>& p)
或按值:void foo(shared_ptr<T> p)
?
我更喜欢第一种方法,因为我怀疑它会更快。但这真的值得,还是有其他问题?
请您说出您选择的理由或案例,为什么您认为无关紧要。
答案 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)
指南:除非将智能指针作为函数参数传递 你想使用或操纵智能指针本身,如 分享或转让所有权。
指南:表示某个功能将存储和共享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)
我运行了下面的代码,一次是foo
,shared_ptr
按const&
,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不够大,它的构造函数\析构函数也没有做足够的工作,因为副本有足够的开销来关注引用传递和复制性能传递。