从类方法返回成员unique_ptr

Returning member unique_ptr from class method

本文关键字:ptr unique 成员 类方法 返回      更新时间:2023-10-16

我正试图将std::unique_ptr class成员(试图移动所有权(返回给调用者。以下是一个示例代码片段:

class A {
public:
  A() : p {new int{10}} {}
  static std::unique_ptr<int> Foo(A &a) {
    return a.p; // ERROR: Copy constructor getting invoked
                // return std::move(a.p); WORKS FINE
  }
  std::unique_ptr<int> p;
};

我认为编译器(gcc-5.2.1(在这种情况下可以进行返回值优化(复制省略(,而不需要通过std::move()进行明确的意图。但事实并非如此。为什么不呢?

以下代码似乎运行良好,这似乎是等效的:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}

[class.copy]中的规则是:

[…]当return语句中的表达式是(可能带括号(id表达式,该表达式命名具有在最内部封闭函数的正文或参数声明子句中声明的自动存储持续时间的对象,或lambda表达式

在本例中:

std::unique_ptr<int> foo() {
  std::unique_ptr<int> p {new int{10}};
  return p;
}

p是在函数体中声明的具有自动存储持续时间的对象的名称。因此,与其将它复制到返回值中,我们首先尝试移动它。这很好。

但在这个例子中:

static std::unique_ptr<int> Foo(A &a) {
    return a.p;
}

这不适用。a.p根本不是对象的名称,所以我们不会把它当作一个右值来尝试重载解析,而是做正常的事情:尝试复制它。这失败了,所以你必须显式地move()它。


这是规则的措辞,但它可能无法回答你的问题。为什么这是规则?基本上,我们是为了安全。如果我们要命名一个局部变量,那么在return语句中从它转移总是安全的。再也无法访问它。易于优化,没有可能的缺点。但在您最初的示例中,a不属于此函数,a.p也不属于。离开它本质上是不安全的,所以语言不会试图自动做到这一点。

由于a.pstd::unique_ptr,因此无法应用复制省略(以及其他原因(,这是不可复制的。由于a.p的寿命超过了A::Foo(A&)的主体,如果编译器自动尝试从a.p转移,这可能会破坏a的类不变量,这将是非常令人惊讶的(例如,对编写代码的人来说是令人惊讶的(。如果您return std::move(a.p);,它会起作用,但这会显式地窃取a.p