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

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

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

两个可能的线索:

  • 早期物体破坏。如果我通过(const)引用返回 shared_ptr,引用计数器不会递增,因此当对象在另一个上下文(例如另一个线程)中超出作用域时,我就会产生删除对象的风险。是这样吗?如果环境是单线程的,那么这种情况也会发生吗?
  • 按值传递当然不是免费的,是否值得在任何可能的情况下避免它?

谢谢大家。

106284 次浏览

Return smart pointers by value.

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

由于 返回值优化(RVO)的存在,目前对成本的担忧已经变得毫无意义,因此在现代编译器中不会出现增量-增量-减量序列或类似的情况。因此,返回 shared_ptr的最佳方法是简单地按值返回:

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

对于现代 C + + 编译器来说,这是一个非常明显的 RVO 机会。我知道一个事实,即使在关闭所有优化时,Visual C + + 编译器也会实现 RVO。对于 C + + 11的 move 语义来说,这个问题就更加无关紧要了。(但唯一能确定的方法就是侧写和实验。)

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

诚实地说: 下面的代码让你感觉如何?

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

坦白地说,即使我应该知道更好,它使我紧张。原则上,当 get_names() 返回,我们必须复制一个 vectorstring。然后,我们需要复制它时,我们初始化 我们需要销毁第一个拷贝。如果向量中有 N 个 string,每个拷贝 在复制字符串内容时,可能需要多达 N + 1的内存分配和大量缓存不友好的数据访问 > 。

与其面对那种焦虑,我经常会回过头去避免引用别人的话 不必要的副本:

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

Unfortunately, this approach is far from ideal.

  • 密码增长了150%
  • 我们不得不放弃 const-ness,因为我们正在改变名字。
  • 正如函数式程序员喜欢提醒我们的那样,通过破坏参照透明度(计算机科学)和等式推理,变异使代码变得更加复杂。
  • 我们不再对名称使用严格的值语义。

但是,真的有必要以这种方式搞乱我们的代码以获得效率吗?幸运的是,答案是否定的(特别是当你使用 C + + 0x 时)。

关于 any智能指针(不仅仅是 share _ ptr) ,我不认为返回一个引用是可以接受的,而且我会非常犹豫通过引用或原始指针传递它们。为什么?因为您不能确定它不会在以后通过引用进行浅拷贝。你的第一点定义了为什么这应该是一个关注的原因。即使在单线程环境中也可能发生这种情况。您不需要并发访问数据来在程序中放置错误的复制语义。一旦传递了指针,您实际上并不能控制用户使用指针做什么,所以不要鼓励误用指针,给 API 用户足够的时间去吊死自己。

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

编辑,3年后: 随着 C + + 中更现代的特性的出现,我会调整我的答案,以便更多地接受这样的情况,当你简单地编写了一个 lambda,从来没有生活在调用函数的范围之外,也没有在其他地方复制。在这里,如果您希望节省复制共享指针的最小开销,那么它将是公平和安全的。为什么?因为您可以保证引用永远不会被误用。