9

我很好奇这个功能到底是如何工作的。考虑类似的东西

std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

即使对于仅移动类型,此代码也可以正常编译,因为编译器会隐式移动它。但从逻辑上讲,对于任何返回表达式,确定结果是否引用局部变量将解决停机问题 - 如果编译器只是将所有局部变量视为返回表达式中的右值,那么这将是有问题的变量可以在一个表达式中多次引用。即使本地只有一个直接引用,您也无法证明它没有其他间接别名。

那么编译器如何知道何时从返回表达式中移动呢?

4

4 回答 4

10

There's a simple rule: If the conditions for copy elision are met (except that the variable may be function parameter), treat as rvalue. If that fails, treat as lvalue. Otherwise, treat as lvalue.

§12.8 [class.copy] p32

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, 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. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

Example:

template<class T>
T f(T v, bool b){
  T t;
  if(b)
    return t; // automatic move
  return v; // automatic move, even though it's a parameter
}

Not that I personally agree with that rule, since there is no automatic move in the following code:

template<class T>
struct X{
  T v;
};

template<class T>
T f(){
  X<T> x;
  return x.v; // no automatic move, needs 'std::move'
}

See also this question of mine.

于 2012-07-15T15:06:47.693 回答
5

该标准没有说“指的是局部变量”,而是说“是非易失性自动对象的名称”。

§12.8 [class.copy] p31

[...] 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的 [...]:

  • return具有类返回类型的函数的语句中,当表达式是非易失性自动对象的名称时[...]

因此,不允许使用指针或引用。如果你以邪恶的方式阅读它,试图利用一种不太可能是有意的解释,你可以说它意味着移动以下内容

struct A {
  std::unique_ptr<int> x;
  std::unique_ptr<int> f() { return x; }
};

int main() { A a; a.f(); }

在这种情况下,返回表达式是具有自动存储持续时间的变量的名称。标准中的其他一些段落可以以多种方式解释,但规则是采用最有可能的解释。

于 2012-07-15T15:10:16.817 回答
3

但从逻辑上讲,对于任何返回表达式,确定结果是否引用局部变量将解决停机问题。

你高估了问题的复杂性。很明显,编译器会查看返回表达式是否提到了本地变量之一,并且它必须跟踪所有这些变量才能调用析构函数。请注意,仅当返回明确提及变量时,它才会移动,如果您返回指针或对局部变量的引用,则不需要这样做:

std::unique_ptr<int>& same( std::unique_ptr<int>& x ) { return x; }
std::unique_ptr<int> foo() {
   std::unique_ptr<int> p( new int );
   std::unique_ptr<int>& r = same(p);
   return r;                           // FAIL
}
于 2012-07-15T16:58:43.073 回答
2

我认为您在这里高估了编译器的能力。

如果你直接返回一个局部变量,那么工作很简单:你可以移动它。

如果您正在调用需要从局部变量移动的表达式,则必须手动指定它。

在此处查看一些示例

于 2012-07-15T15:11:00.120 回答