unique_ptr<T>不允许复制构造,相反,它支持移动语义。但是,我可以从函数返回unique_ptr<T>并将返回值赋给变量。

#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;
}

上面的代码按预期进行编译和工作。那么,为什么第1行没有调用复制构造函数并导致编译器错误呢?如果我不得不使用第2行,那就有意义了(使用第2行也可以,但我们不需要这样做)。

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


当前回答

我知道这是一个老问题,但我认为这里缺少一个重要而明确的参考。

来自https://en.cppreference.com/w/cpp/language/copy_elision:

(自c++ 11起)在return语句或throw表达式中,如果编译器不能执行复制省略,但满足或将满足复制省略的条件(除非源是函数形参),即使对象由左值指定,编译器也将尝试使用move构造函数;详细信息请参见return语句。

其他回答

我想提到一种情况,你必须使用std::move(),否则它会给出一个错误。 Case:如果函数的返回类型与局部变量的类型不同。

class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
     std::unique_ptr<Derived> derived(new Derived());
     return std::move(derived); //std::move() must
}

参考:https://www.chromium.org/developers/smart-pointer-guidelines

我在其他答案中没有看到的一件事是澄清另一个答案,返回std::unique_ptr是在函数中创建的,和给该函数的是有区别的。

例子可以是这样的:

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));

我知道这是一个老问题,但我认为这里缺少一个重要而明确的参考。

来自https://en.cppreference.com/w/cpp/language/copy_elision:

(自c++ 11起)在return语句或throw表达式中,如果编译器不能执行复制省略,但满足或将满足复制省略的条件(除非源是函数形参),即使对象由左值指定,编译器也将尝试使用move构造函数;详细信息请参见return语句。

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

unique_ptr::unique_ptr(unique_ptr && src);

右值引用(双&)只绑定到右值。这就是为什么当您试图将左值unique_ptr传递给函数时,会得到一个错误。另一方面,函数返回的值被视为右值,因此move构造函数被自动调用。

顺便说一下,这将正确工作:

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

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

我认为这在Scott Meyers的《Effective Modern c++》第25条中得到了完美的解释。以下是节选:

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

Here, RVO refers to return value optimization, and if the conditions for the RVO are met means 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 object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::move is implicitly applied to local objects being returned. Scott mentions in item 25 that std::move is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.

在您的例子中,代码显然是RVO的候选对象,因为它返回局部对象p,而且p的类型与返回类型相同,这将导致复制省略。如果编译器选择不省略副本,不管出于什么原因,std::move将被踢到第1行。