假设我有一个类,其方法返回shared_ptr
。
通过引用或值返回它可能有什么好处和缺点?
两种可能的线索:
shared_ptr
,则引用计数器不会递增,因此当它熄灭时我冒着删除对象的风险范围在另一个上下文中(例如另一个线程)。它是否正确?如果环境是单线程的,那么这种情况会发生吗?谢谢大家。
答案 0 :(得分:94)
按值返回智能指针。
正如您所说,如果您通过引用返回它,您将无法正确增加引用计数,这可能会在不适当的时间删除某些内容。仅这一点就足以成为不能通过引用返回的理由。接口应该是健壮的。
由于return value optimization(RVO),成本问题现在没有实际意义,所以你不会在现代编译器中产生递增 - 递减 - 递减序列或类似的东西。因此,返回shared_ptr
的最佳方法是简单地按值返回:
shared_ptr<T> Foo()
{
return shared_ptr<T>(/* acquire something */);
};
对于现代C ++编译器来说,这是一个显而易见的RVO机会。我知道即使关闭所有优化,Visual C ++编译器也会实现RVO。而对于C ++ 11的移动语义,这种担忧甚至更不重要。 (但唯一可以确定的方法是剖析和实验。)
如果你仍然不相信,Dave Abrahams已经an article提出了以价值回归的论据。我在这里复制一个片段;我强烈建议您阅读整篇文章:
说实话:以下代码如何让您感觉到?
坦率地说,尽管我应该知道更好,但这让我感到紧张。原则上,std::vector<std::string> get_names(); ... std::vector<std::string> const names = get_names();
get_names()
时 返回时,我们必须复制vector
个string
。然后,我们需要在初始化时再次复制它names
,我们需要销毁第一个副本。如果向量中有Nstring
个s,则每个副本 可能需要多达N + 1个存储器分配和一大堆不友好的高速缓存数据访问&gt;因为字符串内容被复制了。我没有面对那种焦虑,而是经常躲避传递参考以避免 不必要的副本:
get_names(std::vector<std::string>& out_param ); ... std::vector<std::string> names; get_names( names );
不幸的是,这种做法远非理想。
- 代码增长了150%
- 我们不得不放弃
const
- 因为我们正在改变名字。- 正如功能程序员想要提醒我们的那样,突变会使代码更加复杂,从而破坏参照透明度和等式推理。
- 我们不再对名称有严格的值语义。
但是,是否真的有必要以这种方式搞砸我们的代码以提高效率?幸运的是,答案结果是否定的(特别是如果您使用的是C ++ 0x)。
答案 1 :(得分:18)
关于任何智能指针(不仅仅是shared_ptr),我认为返回一个引用并不可接受,我会非常犹豫通过引用或原始指针传递它们。为什么?因为您无法确定以后不会通过引用进行浅层复制。您的第一点定义了为什么这应该是一个问题的原因。即使在单线程环境中也会发生这种情况。您不需要并发访问数据来在程序中放置错误的复制语义。一旦你将其传递掉,你就无法真正控制用户对指针的操作,所以不要鼓励误操作,让你的API用户有足够的绳索自行挂起。
其次,如果可能,请查看智能指针的实现。建筑和破坏应该接近于可以忽略不计。如果这个开销是不可接受的,那么不要使用智能指针!但除此之外,您还需要检查您已经获得的并发体系结构,因为对跟踪指针使用的机制的互斥访问将减慢您的速度,而不仅仅是构建shared_ptr对象。
编辑,3年后:随着C ++中更现代的功能的出现,我会调整我的答案,当你简单地编写一个永远不会存在于调用函数范围之外的lambda时,更多地接受这种情况,并且不会复制到其他地方。在这里,如果你想节省复制共享指针的最小开销,那将是公平和安全的。为什么?因为您可以保证永远不会误用参考。