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

Posted

技术标签:

【中文标题】如何通过引用或值返回智能指针(shared_ptr)?【英文标题】:How to return smart pointers (shared_ptr), by reference or by value? 【发布时间】:2012-05-25 11:49:39 【问题描述】:

假设我有一个类,其方法返回 shared_ptr

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

两个可能的线索:

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

谢谢大家。

【问题讨论】:

【参考方案1】:

按值返回智能指针。

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

由于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 提出了按价值返回的论据。我在这里复制了一个sn-p;我强烈建议您阅读整篇文章:

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

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

坦率地说,即使我应该知道的更多,这让我感到紧张。原则上,当get_names() 返回,我们必须复制vectorstrings。然后,我们需要在初始化的时候再复制一次 names,我们需要销毁第一个副本。如果向量中有N个strings,每个副本 可能需要多达 N+1 个内存分配和大量对缓存不友好的数据访问 > 因为字符串内容被复制。

我没有面对这种焦虑,而是经常使用传递引用来避免 不必要的副本:

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

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

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

但是为了提高效率真的有必要这样搞乱我们的代码吗?幸运的是,答案是否定的(尤其是如果您使用的是 C++0x)。

【讨论】:

我不知道我会说 RVO 使这个问题变得毫无意义,因为通过引用返回肯定会使 RVO 成为不可能。 @CrazyEddie:没错,这就是我建议 OP 按值返回的原因之一。 标准允许的 RVO 规则是否胜过标准保证的关于同步/发生前关系的规则? @edA-qa mort-ora-y:RVO 是明确允许的,即使它有副作用。例如,如果您在默认构造函数和复制构造函数中有 cout &lt;&lt; "Hello World!"; 语句,则当 RVO 生效时,您不会看到两个 Hello World!s。但是,对于设计合理的智能指针来说,这应该不是问题,即使是 w.r.t.同步。 如果shared_ptr是参数,传值还是常量引用呢?【参考方案2】:

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

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

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

【讨论】:

以上是关于如何通过引用或值返回智能指针(shared_ptr)?的主要内容,如果未能解决你的问题,请参考以下文章

智能指针的模拟实现shared_ptr 循环引用 定置删除器

智能指针循环引用--转

C++11 智能指针

智能指针之 shared_ptr

智能指针weak_ptr记录

C++智能指针shared_ptr 定位删除器(仿函数)