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

比方说,我有一个类返回一个shared_ptr的方法。

通过参考或价值回报可能有哪些好处和弊端?

两个可能的线索:

  • 早期的物体破坏。 如果我通过(const)引用返回shared_ptr ,那么引用计数器不会增加,所以当我们在另一个上下文中(例如另一个线程)超出了范围时,我就有被删除的风险。 它是否正确? 如果环境是单线程的,那么这种情况是否也会发生呢?
  • 成本。 传递价值当然不是免费的。 是否值得尽量避免?

谢谢大家。

按值返回智能指针。

正如你所说的,如果你通过引用返回它,你将不会正确地增加引用计数,这会在不适当的时候删除某些东西。 单靠这个应该有足够的理由不能通过参考返回。 接口应该是健壮的。

目前的成本问题由于返回值优化 (RVO)而没有实际意义,因此您不会在现代编译器中产生增量递增递减序列或类似的东西。 所以返回一个shared_ptr的最好方法是简单地按值返回:

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

对于现代C ++编译器来说,这是一个显而易见的RVO机会。 我知道一个事实,即使所有的优化都被closures,Visual C ++编译器也实现了RVO。 而随着C ++ 11的移动语义,这种担心甚至不太重要。 (但唯一确定的方法是进行configuration和实验。)

如果你还不相信,戴夫·亚伯拉罕(Dave Abrahams)有一篇文章提出了按价值回报的论点。 我在这里再现一个片段。 我强烈build议你去阅读整篇文章:

说实话:下面的代码是如何让你感觉的?

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

坦率地说,即使我应该知道更好,这让我感到紧张。 原则上,当get_names()返回时,我们必须复制一个string s的vector 。 然后,我们需要在初始化names时再次复制它,并且我们需要销毁第一个副本。 如果向量中有N个string ,每个副本可能需要多达N + 1个内存分配以及整个转储的caching不友好的数据访问>因为string内容被复制。

我经常为了避免不必要的副本而回避引用,而不是面对这种焦虑。

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

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

  • 代码增长了150%
  • 我们不得不放弃const因为我们在改变名字。
  • 正如函数程序员提醒我们的那样,变异使代码更加复杂,通过破坏参照透明性和等式推理来推理。
  • 我们不再有严格的名称价值语义。

但是为了获得效率,是否真的需要以这种方式搞乱我们的代码呢? 幸运的是,答案是否定的(特别是如果你使用C ++ 0x)。

关于任何智能指针(不只是shared_ptr),我不认为回到一个引用是不可接受的,我会非常犹豫地通过引用或原始指针传递它们。 为什么? 因为你不能确定它不会被稍后通过引用浅拷贝。 你的第一点定义了为什么这应该是一个问题。 即使在单线程环境中也可能发生这种情况。 您不需要并发访问数据来在您的程序中添加坏的复制语义。 一旦closures指针,用户不会真正控制用户的操作,因此,不要鼓励滥用,让API用户有足够的绳索来挂起自己。

其次,如果可能的话,看看你的智能指针的实现。 build设和破坏应该是接近可以忽略不计的。 如果这种开销是不可接受的,那么不要使用智能指针! 但是,除此之外,还需要检查已有的并发体系结构,因为对跟踪指针使用的机制的互斥访问将会比单纯构buildshared_ptr对象慢得多。

编辑,3年后:随着C + +更现代的function的到来,我会调整我的答案更接受的情况下,当你只是写一个lambda永远不会超出调用函数的作用域,而不是复制到其他地方。 在这里,如果你想保存复制一个共享指针的最小开销,这将是公平和安全的。 为什么? 因为你可以保证参考不会被误用。