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,而是适用于任何可移动的类。这是由语言规则保证的,因为你是通过值返回的。编译器尝试省略副本,如果不能删除副本,则调用移动构造函数,如果不能移动则调用复制构造函数,如果不能复制则编译失败。

如果你有一个接受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;
}

在语言规范中是否有其他子句可以利用?

是的,见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 object with 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语句中的命名值,即在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

通过在编译时设置标志,这个函数中会发生两次移动(1和2),然后再发生一次移动(3)。

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

unique_ptr::unique_ptr(unique_ptr && src);

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

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

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

这里的临时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::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