我试图理解为什么以下代码不会调用我的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应该调用移动构造函数。
这是因为编译器只是通过忽略我的移动构造函数定义来优化,还是因为我的一个假设是有缺陷的?
答案 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并绕过使用移动构造函数返回对象的需要。