提问者:小点点

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


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

通过引用或值返回它可能有哪些好处和缺点?

两条可能的线索:

    < li >早期对象销毁。如果我通过(const)引用返回< code>shared_ptr,引用计数器不会递增,所以当对象超出另一个上下文(例如另一个线程)的范围时,我会面临对象被删除的风险。这是正确的吗?如果环境是单线程的,这种情况也会发生吗? < li >成本。按值传递当然不是免费的。是否值得尽可能避免?

谢谢大家。


共2个答案

匿名用户

通过值返回智能指针。

正如您所说,如果您通过引用返回它,您将无法正确增加引用计数,这就增加了在不合适的时间删除某些内容的风险。仅此一项就应该足以成为不通过引用返回的理由。接口应该是健壮的。

由于返回值优化(RVO),目前对成本的担忧是没有意义的,因此在现代编译器中不会产生递增-递增-递减序列或类似的情况。因此,返回shared_ptr的最佳方法是简单地按值返回:

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

对于现代C编译器来说,这是一个非常明显的RVO机会。我知道一个事实,Visual C编译器实现了RVO,即使所有的优化都关闭了。而有了C 11的move语义,这种担心就更不相关了。(但唯一能确定的方法是侧写和实验。)

如果您仍然不相信,Dave Abrahams有一篇文章提出了按值返回的论点。我在这里复制了一个片段;我强烈建议您阅读整篇文章:

实话实说:下面这段代码让你有什么感觉?

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

坦白地说,尽管我应该知道得更清楚,但这让我很紧张。原则上,当< code>get_names()返回时,我们必须复制一个< code>string的< code>vector。然后,我们需要在初始化< code>names时再次复制它,并且我们需要销毁第一个副本。如果vector中有N个< code>string,则每个副本可能需要多达N-1个内存分配和大量对缓存不友好的数据访问

我没有直面这种焦虑,而是经常依靠引用传递来避免不必要的复制:

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

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

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

但是为了获得效率,真的有必要用这种方式把代码弄乱吗?幸运的是,答案是否定的(尤其是在使用C 0x的情况下)。

匿名用户

关于任何智能指针(不仅仅是shared_ptr),我认为返回对一个指针的引用是不可接受的,我非常犹豫是否通过引用或原始指针传递它们。为什么?因为你不能确定它不会被稍后通过引用浅拷贝。你的第一点定义了为什么这应该是一个问题。即使在单线程环境中也可能发生这种情况。你不需要对数据的并发访问来将糟糕的复制语义学放入你的程序中。一旦你传递了指针,你就不能真正控制你的用户用它做什么,所以不要鼓励滥用,给你的API用户足够的绳子来上吊。

第二,如果可能,查看智能指针的实现。建设和破坏应该是微不足道的。如果这个开销不可接受,那么不要使用智能指针!但除此之外,您还需要检查现有的并发体系结构,因为对跟踪指针使用情况的机制的互斥访问将比仅构建shared_ptr对象更慢。

编辑,3年后:随着C语言中更现代特性的出现,我会调整我的答案,以便更容易接受当你简单地编写了一个永远不会超出调用函数范围的lambda,并且不会复制到其他地方的情况。在这里,如果你想节省复制共享指针的最小开销,这将是公平和安全的。为什么?因为你可以保证引用永远不会被错误使用。