编译器忽略显式定义的移动构造函数?

时间:2016-04-16 22:15:34

标签: c++ c++11 move

我试图理解为什么以下代码不会调用我的move-constructor。我用gnu ++ 11编译。

#include <iostream>

class Foo{
  int value;
  public:
  Foo(int v){ value = v; }
  Foo(const Foo& g){ 
    value = g.value; 
    std::cout << "copy construct called\n"; 
  }
  Foo(Foo&& g){ 
    value = g.value; 
    std::cout << "move construct called\n"; 
  }
  int getValue() const{ return value; }
};

Foo operator+(const Foo& f, const Foo& g){
  Foo h(f.getValue() + g.getValue());
  return h;
}

void sayValue(const Foo& f){
  std::cout << f.getValue() << std::endl;
}

int main(){
  Foo f(5);
  sayValue(f + f);
  return 0;
} 

运行以下代码只会打印10而不是

move construct called
10

正如我所料。我期望的原因是因为当我为Foo定义+二元运算符时,如果我理解正确的话,返回Foo g应该调用移动构造函数。

这是因为编译器只是通过忽略我的移动构造函数定义来优化,还是因为我的一个假设是有缺陷的?

2 个答案:

答案 0 :(得分:3)

在C ++中有一个称为elision的概念。

Elision允许编译器将各种变量的生命周期加入到一个生命周期中 - 它们的存在被一起消除。

它可以省略一个临时用于直接构造相同类型的值,或者在简单的return x;样式语句中从函数返回的命名局部变量。

使其合法的构造函数必须存在,但编译器不需要调用它。即使复制或移动ctor会产生副作用,也允许使用Elision,因此您的print语句不会运行。

所以编译器没有移动 - 它只是构建了直接进入的对象!值h实际上是+的实际返回值。

你做完了

Foo x = f+f;

编译器可以将h+的返回值放在一个对象中。{/ p>

可以追溯到一段时间的两个常见案例是NRVO和RVO(命名返回值优化和返回calue优化),但它们只是编译器做的特定技术的名称。编译器不会被迫忽略,但是被允许。在某些情况下,编译器难以进行省略(并且在一种情况下不允许IIRC);在每种情况下,都可以调用移动ctor(如果不存在移动则复制)。例如,当您从函数返回两个不同的局部变量时;它使编译器很难合法地使用返​​回值。

答案 1 :(得分:0)

Foo operator+(const Foo& f, const Foo& g){
    Foo h(f.getValue() + g.getValue());
    return h;
}

这将调用NRVO并绕过使用移动构造函数返回对象的需要。