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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10643563/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:17:36  来源:igfitidea点击:

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

c++returnsmart-pointers

提问by Vincenzo Pii

Let's say I have a class with a method that returns a shared_ptr.

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

What are the possible benefits and drawbacks of returning it by reference or by value?

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

Two possible clues:

两个可能的线索:

  • Early object destruction.If I return the shared_ptrby (const) reference, the reference counter is not incremented, so I incur the risk of having the object deleted when it goes out of scope in another context (e.g. another thread). Is this correct? What if the environment is single-threaded, can this situation happen as well?
  • Cost.Pass-by-value is certainly not free. Is it worth avoiding it whenever possible?
  • 早期对象销毁。如果我返回shared_ptrby (const) 引用,则引用计数器不会增加,因此当对象在另一个上下文(例如另一个线程)中超出范围时,我会面临被删除的风险。这样对吗?如果环境是单线程的,这种情况也会发生吗?
  • 成本。按值传递肯定不是免费的。是否值得尽可能避免它?

Thanks everybody.

谢谢大家。

回答by In silico

Return smart pointers by value.

按值返回智能指针。

As you've said, if you return it by reference, you won't properly increment the reference count, which opens up the risk of deleting something at the improper time. That alone should be enough reason to not return by reference. Interfaces should be robust.

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

The cost concern is nowadays moot thanks to return value optimization(RVO), so you won't incur a increment-increment-decrement sequence or something like that in modern compilers. So the best way to return a shared_ptris to simply return by value:

由于返回值优化(RVO),成本问题现在没有实际意义,因此您不会在现代编译器中产生增量-增量-减量序列或类似的东西。所以返回 a 的最好方法shared_ptr是简单地按值返回:

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

This is a dead-obvious RVO opportunity for modern C++ compilers. I know for a fact that Visual C++ compilers implement RVO even when all optimizations are turned off. And with C++11's move semantics, this concern is even less relevant. (But the only way to be sure is to profile and experiment.)

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

If you're still not convinced, Dave Abrahams has an articlethat makes an argument for returning by value. I reproduce a snippet here; I highly recommend that you go read the entire article:

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

Be honest: how does the following code make you feel?

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

Frankly, even though I should know better, it makes me nervous. In principle, when get_names()returns, we have to copy a vectorof strings. Then, we need to copy it again when we initialize names, and we need to destroy the first copy. If there are N strings in the vector, each copy could require as many as N+1 memory allocations and a whole slew of cache-unfriendly data accesses > as the string contents are copied.

Rather than confront that sort of anxiety, I've often fallen back on pass-by-reference to avoid needless copies:

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

Unfortunately, this approach is far from ideal.

  • The code grew by 150%
  • We've had to drop const-ness because we're mutating names.
  • As functional programmers like to remind us, mutation makes code more complex to reason about by undermining referential transparency and equational reasoning.
  • We no longer have strict value semantics for names.

But is it really necessary to mess up our code in this way to gain efficiency? Fortunately, the answer turns out to be no (and especially not if you are using C++0x).

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

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

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

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

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

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

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

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

回答by San Jacinto

Regarding anysmart pointer (not just shared_ptr), I don't think it's ever acceptable to return a reference to one, and I would be very hesitant to pass them around by reference or raw pointer. Why? Because you cannot be certain that it will not be shallow-copied via a reference later. Your first point defines the reason why this should be a concern. This can happen even in a single-threaded environment. You don't need concurrent access to data to put bad copy semantics in your programs. You don't really control what your users do with the pointer once you pass it off, so don't encourage misuse giving your API users enough rope to hang themselves.

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

Secondly, look at your smart pointer's implementation, if possible. Construction and destruction should be darn close to negligible. If this overhead isn't acceptable, then don't use a smart pointer! But beyond this, you will also need to examine the concurrency architecture that you've got, because mutually exclusive access to the mechanism that tracks the uses of the pointer is going to slow you down more than mere construction of the shared_ptr object.

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

Edit, 3 years later: with the advent of the more modern features in C++, I would tweak my answer to be more accepting of cases when you've simply written a lambda that never lives outside of the calling function's scope, and isn't copied somewhere else. Here, if you wanted to save the very minimal overhead of copying a shared pointer, it would be fair and safe. Why? Because you can guarantee that the reference will never be mis-used.

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