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

compiler ignores explicitly-defined move constructor?

本文关键字:移动 构造函数 定义 编译器      更新时间:2023-10-16

我试图理解为什么下面的代码没有调用我的move构造函数。我使用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 calledn"; 
  }
  Foo(Foo&& g){ 
    value = g.value; 
    std::cout << "move construct calledn"; 
  }
  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应该调用move构造函数。

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

在C++中有一个概念叫做省略。

Elision允许编译器将各种变量的生命期连接到一个生命期中——它们的存在被忽略在一起。

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

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

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

你做过吗

Foo x = f+f;

编译器可以将h+x的返回值消除为一个对象。


有两种常见的情况可以追溯到一段时间前,即NRVO和RVO(命名为返回值优化和返回calue优化),但它们只是编译器要消除的特定技术的名称。编译器不被强制省略,但被允许省略。在某些情况下,省略对编译器来说很困难(在一种情况下,IIRC是不允许的);在每种情况下,都可以调用move ctor(如果不存在move,则可以调用copy)。一个例子是,当您有两个不同的局部变量从一个函数返回时;这使得编译器很难合法地消除这两个返回值。

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

这调用了NRVO,并绕过了使用move构造函数返回对象的需要。