编译器如何知道移动局部变量?

时间:2012-07-15 14:59:47

标签: c++ c++11 move-semantics

我很好奇这个功能是如何工作的。考虑像

这样的东西
std::unique_ptr<int> f() { std::unique_ptr<int> lval(nullptr); return lval; }

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

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

4 个答案:

答案 0 :(得分:10)

有一个简单的规则:如果满足复制省略的条件(变量可能是函数参数除外),则视为右值。如果失败,则视为左值。否则,视为左值。

§12.8 [class.copy] p32

  

当满足或将满足复制操作的省略标准时,除了源对象是函数参数之外,并且要复制的对象由左值指定,重载决策要选择首先执行副本的构造函数,就像对象是由右值指定的一样。如果重载决策失败,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行此两阶段重载决策。如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数。 -end note ]

示例:

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
}

并非我个人同意该规则,因为以下代码中没有自动移动:

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

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

另见this question of mine

答案 1 :(得分: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(); }

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

答案 2 :(得分: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
}

答案 3 :(得分:2)

我认为你高估了编译器的容量。

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

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

请参阅some examples here