C++ 从函数返回 unique_ptr

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4316727/
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-28 15:06:43  来源:igfitidea点击:

Returning unique_ptr from functions

c++c++11unique-ptr

提问by Praetorian

unique_ptr<T>does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T>from a function and assign the returned value to a variable.

unique_ptr<T>不允许复制构造,而是支持移动语义。但是,我可以unique_ptr<T>从函数返回 a并将返回的值分配给变量。

#include <iostream>
#include <memory>

using namespace std;

unique_ptr<int> foo()
{
  unique_ptr<int> p( new int(10) );

  return p;                   // 1
  //return move( p );         // 2
}

int main()
{
  unique_ptr<int> p = foo();

  cout << *p << endl;
  return 0;
}

The code above compiles and works as intended. So how is it that line 1doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2instead it'd make sense (using line 2works as well, but we're not required to do so).

上面的代码编译并按预期工作。那么该行如何1不调用复制构造函数并导致编译器错误?如果我不得不使用 line2代替它会有意义(使用 line2也可以,但我们不需要这样做)。

I know C++0x allows this exception to unique_ptrsince the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?

我知道 C++0x 允许这个异常,unique_ptr因为返回值是一个临时对象,一旦函数退出就会被销毁,从而保证返回指针的唯一性。我很好奇这是如何实现的,它是编译器中的特殊情况,还是语言规范中是否有其他条款可以利用?

采纳答案by fredoverflow

is there some other clause in the language specification that this exploits?

语言规范中是否还有其他条款可以利用?

Yes, see 12.8 §34 and §35:

是的,请参阅 12.8 §34 和 §35:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...] This elision of copy/move operations, called copy elision, is permitted [...] in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic objectwith the same cv-unqualified type as the function return type [...]

When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

当满足某些条件时,允许实现省略类对象的复制/移动构造 [...] 这种复制/移动操作的省略,称为复制省略,在 [...] 中的 return 语句中是允许的具有类返回类型的函数,当表达式是具有与函数返回类型相同的 cv 非限定类型的非易失性自动对象的名称时[...]

当满足复制操作的省略条件并且要复制的对象由左值指定时,首先执行重载决议以选择复制的构造函数,就像对象由 rvalue 指定一样



Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the -fno-elide-constructorsflag

只是想再补充一点,按值返回应该是这里的默认选择,因为在最坏的情况下,返回语句中的命名值,即在 C++11、C++14 和 C++17 中没有省略被处理作为右值。因此,例如以下函数使用-fno-elide-constructors标志编译

std::unique_ptr<int> get_unique() {
  auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
  return ptr; // <- 2, moved into the to be returned unique_ptr
}

...

auto int_uptr = get_unique(); // <- 3

With the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).

在编译时设置标志后,此函数中发生了两次移动(1 和 2),然后在 (3) 中进行了一次移动。

回答by Nikola Smiljani?

This is in no way specific to std::unique_ptr, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.

这绝非特定于std::unique_ptr,而是适用于任何可移动的类。这是由语言规则保证的,因为您是按值返回的。编译器尝试省略副本,如果不能删除副本,则调用移动构造函数,如果不能移动则调用复制构造函数,如果不能复制则编译失败。

If you had a function that accepts std::unique_ptras an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call to bar().

如果您有一个接受std::unique_ptr作为参数的函数,您将无法将 p 传递给它。您必须显式调用移动构造函数,但在这种情况下,您不应在调用bar().

void bar(std::unique_ptr<int> p)
{
    // ...
}

int main()
{
    unique_ptr<int> p = foo();
    bar(p); // error, can't implicitly invoke move constructor on lvalue
    bar(std::move(p)); // OK but don't use p afterwards
    return 0;
}

回答by Bartosz Milewski

unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:

unique_ptr 没有传统的复制构造函数。相反,它有一个使用右值引用的“移动构造函数”:

unique_ptr::unique_ptr(unique_ptr && src);

An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.

右值引用(双与号)只会绑定到右值。这就是为什么当您尝试将左值 unique_ptr 传递给函数时会出现错误的原因。另一方面,从函数返回的值被视为右值,因此会自动调用移动构造函数。

By the way, this will work correctly:

顺便说一句,这将正常工作:

bar(unique_ptr<int>(new int(44));

The temporary unique_ptr here is an rvalue.

这里的临时 unique_ptr 是一个右值。

回答by David Lee

I think it's perfectly explained in item 25of Scott Meyers' Effective Modern C++. Here's an excerpt:

我认为它在Scott Meyers 的Effective Modern C++25 条中得到了完美的解释。这是摘录:

The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::moveis implicitly applied to local objects being returned.

标准祝福 RVO 的部分继续说,如果满足 RVO 的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值。实际上,标准要求当 RVO 被允许时,要么发生复制省略,要么std::move隐式应用于返回的本地对象。

Here, RVOrefers to return value optimization, and if the conditions for the RVO are metmeans returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local objectincludes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::moveis implicitly applied to local objects being returned. Scott mentions in item 25 that std::moveis implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.

在这里,RVO指的是返回值优化如果满足 RVO 的条件,则意味着返回在您期望执行RVO的函数内部声明的本地对象,这在他的书的第 25 条中也有很好的解释,参考标准(这里的本地对象包括由 return 语句创建的临时对象)。摘录的最大收获是要么发生复制省略,要么std::move隐式应用于返回的本地对象。Scott 在第 25 条中提到,std::move当编译器选择不删除副本并且程序员不应显式这样做时,它会隐式应用。

In your case, the code is clearly a candidate for RVOas it returns the local object pand the type of pis the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason, std::movewould've kicked in to line 1.

在您的情况下,该代码显然是RVO的候选者,因为它返回本地对象p并且类型p与返回类型相同,这会导致复制省略。如果编译器选择不删除副本,无论出于何种原因,std::move都会进入 line 1

回答by v010dya

One thing that i didn't see in other answers isTo clarify another answersthat there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.

我在其他答案中没有看到的一件事是澄清另一个答案,即返回在函数中创建的 std::unique_ptr 与返回给该函数的 std::unique_ptr 之间存在差异。

The example could be like this:

这个例子可能是这样的:

class Test
{int i;};
std::unique_ptr<Test> foo1()
{
    std::unique_ptr<Test> res(new Test);
    return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
    // return t;  // this will produce an error!
    return std::move(t);
}

//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));