如何按引用或值返回智能指针 (shared_ptr)

How to return smart pointers (shared_ptr), by reference or by value?

本文关键字:shared ptr 指针 智能 引用 何按 返回      更新时间:2023-10-16

假设我有一个类,其中包含一个返回shared_ptr的方法。

通过引用或按值返回它可能有什么好处和缺点?

两个可能的线索:

  • 早期对象销毁。如果我返回 shared_ptr by (const( 引用,则引用计数器不会递增,因此当对象在另一个上下文(例如另一个线程(超出范围时,我会产生删除对象的风险。这是对的吗?如果环境是单线程的,这种情况也会发生吗?
  • 成本。按值传递当然不是免费的。是否值得尽可能避免它?

谢谢大家。

按值返回智能指针。

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

由于返回值优化 (RVO(,成本问题现在没有意义,因此您不会在现代编译器中产生增量-增量-递减序列或类似的东西。因此,返回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() 返回时,我们必须复制 string s 的vector。然后,我们需要在初始化时再次复制它 names,我们需要销毁第一个副本。如果向量中有 N string,则每个拷贝 在复制字符串内容时,可能需要多达 N+1 个内存分配和大量缓存不友好的数据访问>。

与其面对这种焦虑,我经常回避通过参考以避免 不必要的副本:

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

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

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

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

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

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

编辑,3 年后:随着 C++ 年更现代功能的出现,我会调整我的答案,以便更能接受当你只是编写一个永远不会存在于调用函数范围之外的 lambda 并且没有复制到其他地方的情况。在这里,如果您想节省复制共享指针的最小开销,这将是公平和安全的。为什么?因为您可以保证引用永远不会被滥用。