从类方法返回成员unique_ptr

13

我试图返回一个std::unique_ptr类成员变量(尝试移动所有权)给调用者。以下是样例代码片段:

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

2
这是一个非常好的第一个问题。欢迎来到StackOverflow! - Barry
2个回答

11
[class.copy]中的规则是:

[...] 当return语句中的表达式是一个(可能带括号的)指向内层封闭函数或lambda表达式id-expression,它命名了在自动存储期内声明的对象时,选择复制构造函数的重载解析首先被执行,就好像该对象被一个右值指定。

在这个例子中:
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()。”
这是规则的措辞,但可能无法回答您的问题。为什么要这样做?基本上——我们试图保持安全。如果我们命名一个局部变量,在返回语句中从中移动始终是安全的。它将不再被访问。容易优化,没有任何可能的缺点。但在您最初的示例中,a既不属于该函数,也不属于a.p。从它那里移动并不是本质上安全的,因此语言不会尝试自动执行它。

-1

由于a.p是一个不可复制的std::unique_ptr,因此无法应用复制省略(除其他原因外)。而且,由于a.p的生命周期超出了A::Foo(A&)函数体之外,如果编译器自动尝试从a.p移动,这将非常令人惊讶(即使是编写代码的人也会感到惊讶),这可能会破坏a的类不变量。如果您使用return std::move(a.p);,它会起作用,但这明确地窃取了a.p


复制省略可以应用于仅可移动类型(例如 OP 的第二个示例)。 - Barry
@Barry,好的,我的第一句话是错误的。复制省略也适用于移动操作。然而,我认为答案的其余部分仍然正确。 - Andre Kostur
@AndreKostur:由于编码人员明确尝试返回一个复制构造函数被删除的unique_ptr,唯一的选择是进行移动操作,拷贝省略应该可以正常工作(在我看来)。实际让我惊讶的是编译错误而不是警告。 - axg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接