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这个异常,因为返回值是一个临时对象,一旦函数退出就会被销毁,从而保证了返回指针的唯一性。我很好奇这是如何实现的,它在编译器中是特殊的,还是在语言规范中有一些其他的子句,这利用了?


当前回答

我在其他答案中没有看到的一件事是澄清另一个答案,返回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));

其他回答

我在其他答案中没有看到的一件事是澄清另一个答案,返回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));

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

如果你有一个接受std::unique_ptr作为参数的函数,你就不能把p传递给它。你必须显式地调用move构造函数,但在这种情况下,你不应该在调用bar()之后使用变量p。

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

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

unique_ptr::unique_ptr(unique_ptr && src);

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

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

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

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

我想提到一种情况,你必须使用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

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

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

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