ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

两个可能的线索:

早期对象破坏。如果我通过(const)引用返回shared_ptr,则引用计数器不会增加,因此当对象超出另一个上下文(例如另一个线程)的范围时,我会承担删除对象的风险。这个对吗?如果环境是单线程的,会不会也出现这种情况呢?

成本。价值传递当然不是免费的。是否值得尽可能避免它?

谢谢大家。


R
Ray Hulha

按值返回智能指针。

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

多亏了 return value optimization (RVO),成本问题现在已经没有实际意义,因此您不会在现代编译器中产生增量-增量-减量序列或类似的东西。所以返回 shared_ptr 的最佳方法是简单地按值返回:

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

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

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

老实说:下面的代码让你感觉如何? std::vector get_names(); ... std::vector const names = get_names();坦率地说,即使我应该知道得更多,这让我感到紧张。原则上,当 get_names() 返回时,我们必须复制一个字符串向量。然后,我们需要在初始化names时再次复制它,并且我们需要销毁第一个副本。如果向量中有 N 个字符串,每个副本可能需要多达 N+1 个内存分配和大量缓存不友好的数据访问 > 因为字符串内容被复制。为了避免不必要的复制,我经常求助于传递引用,而不是面对那种焦虑: get_names(std::vector& out_param ); ... std::vector 名称;获取名称(名称);不幸的是,这种方法远非理想。代码增长了 150% 我们不得不放弃 const-ness,因为我们正在改变名称。正如函数式程序员喜欢提醒我们的那样,变异通过破坏引用透明性和等式推理,使代码的推理变得更加复杂。我们不再有名称的严格值语义。但是真的有必要这样搞乱我们的代码来提高效率吗?幸运的是,答案是否定的(尤其是在使用 C++0x 时)。


我不知道我会说 RVO 使问题变得毫无意义,因为通过引用返回肯定会使 RVO 成为不可能。
@CrazyEddie:没错,这就是我建议 OP 按值返回的原因之一。
标准允许的 RVO 规则是否胜过标准保证的有关同步/发生前关系的规则?
@edA-qa mort-ora-y:RVO 是明确允许的,即使它有副作用。例如,如果您在默认构造函数和复制构造函数中有一条 cout << "Hello World!"; 语句,则当 RVO 生效时,您不会看到两个 Hello World!。然而,这对于正确设计的智能指针来说应该不是问题,即使是同步也是如此。
如果 shared_ptr 是参数,按值传递或 const 引用怎么办?
S
San Jacinto

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

其次,如果可能的话,看看你的智能指针的实现。建造和破坏应该几乎可以忽略不计。如果这个开销是不可接受的,那么不要使用智能指针!但除此之外,您还需要检查您所拥有的并发架构,因为对跟踪指针使用的机制的互斥访问会比仅仅构建 shared_ptr 对象更慢。

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