如何通过引用或值返回智能指针(shared_ptr)?


94

假设我有一个带有返回a的方法的类shared_ptr

通过引用或按值退还它可能有什么利弊?

两种可能的线索:

  • 早期物体破坏。如果我返回shared_ptrby(const)引用,则引用计数器不会增加,因此当对象超出另一个上下文(例如,另一个线程)的范围时,我将承担删除该对象的风险。这样对吗?如果环境是单线程的,该情况也会发生吗?
  • 成本。价值传递当然不是免费的。是否有可能在任何可能的情况下避免它?

谢谢大家。

Answers:


114

按值返回智能指针。

如您所说,如果您通过引用将其返回,将不会适当地增加引用计数,从而增加了在不适当的时间删除某些内容的风险。仅凭这一点就足以成为不作为参考返回的理由。接口应该健壮。

如今,由于返回值优化(RVO),成本问题变得毫无意义,因此您不会产生增量-增量-减量序列或现代编译器中的类似过程。因此,返回a的最佳方法shared_ptr是简单地按值返回:

shared_ptr<T> Foo()
{
    return shared_ptr<T>(/* acquire something */);
};

对于现代C ++编译器来说,这是一个非常明显的RVO机会。我知道,即使关闭所有优化,Visual C ++编译器仍会实现RVO。而使用C ++ 11的移动语义,这种关注就不再那么重要了。(但唯一可以确定的方法是剖析和试验。)

如果您仍然不确定,Dave Abrahams 的文章将论证按值返回。我在这里复制一个片段;我强烈建议您阅读全文:

老实说:以下代码会让您感觉如何?

std::vector<std::string> get_names();
...
std::vector<std::string> const names = get_names();

坦白说,即使我应该更了解,也让我感到紧张。原则上,当get_names() 返回时,我们要复制vectorstring秒。然后,我们需要在初始化时再次复制它 names,并且需要销毁第一个副本。如果string向量中有N s,则每个副本可能需要多达N + 1个内存分配,并且在复制字符串内容时,整个缓存不友好的数据访问都需要>。

与其面对这种焦虑,不如我经常放弃引用传递以避免不必要的复制:

get_names(std::vector<std::string>& out_param );
...
std::vector<std::string> names;
get_names( names );

不幸的是,这种方法远非理想。

  • 代码增长了150%
  • 我们必须放弃const-ness,因为我们正在对名称进行突变。
  • 正如函数程序员喜欢提醒我们的那样,通过破坏引用透明性和方程式推理,变异使代码的推理更加复杂。
  • 我们不再对名称使用严格的值语义。

但是,是否真的有必要以这种方式弄乱我们的代码以提高效率?幸运的是,答案是“否”(尤其是如果您使用的是C ++ 0x)。


我不知道我会说RVO引起了这个问题的争议,因为通过引用返回绝对使RVO成为不可能。
爱德华·斯特朗奇

@CrazyEddie:是的,这就是为什么我建议按值返回OP的原因之一。
计算机上的2012年

该标准允许的RVO规则是否胜过该标准所保证的有关同步/事前关系的规则?
edA-qa mort-ora-y 2012年

1
@ edA-qa mort-ora-y:即使有副作用,也明确允许使用RVO。例如,如果您cout << "Hello World!";在默认复制构造器中有一条语句,那么Hello World!当RVO生效时,您将看不到两个。但是,对于正确设计的智能指针,即使是wrt同步,这也不是问题。
计算机上2012年

23

关于任何智能指针(不仅是shared_ptr),我都不认为返回对一个指针的引用是可以接受的,而且我非常不愿意通过引用或原始指针来传递它们。为什么?因为您不能确定以后不会通过引用将其浅复制。您的第一点定义了应引起关注的原因。即使在单线程环境中也可能发生这种情况。您不需要并发访问数据即可在程序中放置错误的副本语义。一旦将指针传递出去,您就无法真正控制用户的操作,因此,请勿鼓励滥用为API用户提供足够的绳索以使其自身吊起。

其次,如果可能的话,查看您的智能指针的实现。建造和破坏应几乎可以忽略不计。如果这种开销是不可接受的,那么不要使用智能指针!但是除此之外,您还需要检查现有的并发体系结构,因为对跟踪指针使用情况的机制的互斥访问不仅会使shared_ptr对象的构造慢下来,而且会减慢您的工作速度。

编辑,三年后:随着C ++中更现代的功能的出现,我将调整答案,以便更多地接受这样的情况:您只编写了一个lambda,而该lambda永远不会超出调用函数的范围,并且不会复制到其他地方。在这里,如果您希望节省复制共享指针的非常少的开销,那将是公平且安全的。为什么?因为您可以保证引用不会被滥用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.