在代码优化过程中,C++11编译器是否会在可能的情况下将局部变量转换为右值

Do C++11 compilers turn local variables into rvalues when they can during code optimization?

本文关键字:情况下 局部变量 转换 过程中 代码优化 C++11 编译器 是否      更新时间:2023-10-16

例如,有时将复杂或长的表达式拆分为多个步骤是明智的(第二个版本并不更清楚,但它只是一个示例):

return object1(object2(object3(x)));

可以写成:

object3 a(x);
object2 b(a);
object1 c(b);
return c;

假设所有3个类都实现了以右值为参数的构造函数,那么第一个版本可能会更快,因为临时对象是传递的,并且可以移动。我假设在第二个版本中,局部变量被认为是左值。但是,如果以后不使用这些变量,C++11编译器是否会优化代码,使变量被视为右值,并且两个版本的工作方式完全相同?我最感兴趣的是Visual Studio 2013的C++编译器,但我也很高兴知道GCC编译器在这方面的表现。

谢谢,Michal

在这种情况下,编译器不能破坏"好像"规则。但是你可以使用std::move来达到想要的效果:

object3 a(x);
object2 b(std::move(a));
object1 c(std::move(b));
return c;

正如juancapanza所说,编译器不能(在C++级别)违反"好像"规则;也就是说,所有的转换都应该生成语义等效的代码。

然而,在C++级别之外,当代码被优化时,可能会出现更多的机会。

因此,这实际上取决于对象本身:如果移动构造函数/析构函数有副作用,而(去)分配内存是副作用,那么优化就无法进行。如果只使用带有默认移动构造函数/析构函数的POD,那么它可能会自动优化。

但是如果以后不使用这些变量,C++11编译器会优化吗代码,因此变量被认为是右值,并且两者都是版本工作完全相同?

这是可能的,但很大程度上取决于您的类型考虑以下POD类型为point:的示例

#include <cstdio>
struct point {
int x;
int y;
};
static point translate(point p, int dx, int dy) {
return { p.x + dx, p.y + dy };
}
static point mirror(point p) {
return { -p.x, -p.y };
}
static point make_point(int x, int y) {
return { x, y };
}
int main() {
point a = make_point(1, 2);
point b = translate(a, 3, 3);
point c = mirror(b);
std::printf("(x,y) = (%d,%d)n", c.x, c.y);
}

我看了汇编代码,以下是整个程序(!)基本上编译成的内容(因此下面的代码是生成的汇编代码的C近似值):

int main() {
std::printf("(x,y) = (-4,-5)n");
}

它不仅去掉了所有的局部变量,还在编译时进行计算我试过gcc和clang,但没有试过msvc。

好吧,让我们把程序变得更复杂一点,这样它就不能进行计算:

int main(int argc, char* argv[]) {
int x = *argv[1]-'0';
int y = *argv[2]-'0';
point a = make_point(x,y);
point b = translate(a, 3, 3);
point c = mirror(b);
std::printf("(x,y) = (%d,%d)n", c.x, c.y);
}

要运行此代码,您必须像./a.out 1 2一样调用它。

整个程序在优化后简化为这个程序(用C重写程序集):

int main(int argc, char* argv[]) {
int x = *argv[1]-'0';
int y = *argv[2]-'0';
std::printf("(x,y) = (%d,%d)n", -(x+3), -(y+3));
}

因此,它去掉了a, b, c和所有函数make_point()translate()mirror(),并在编译时尽可能多地进行计算

由于Matthieu M.的回答中提到的原因,不要期望对更复杂的类型(尤其是非POD)进行如此好的优化。

根据我的经验,内联是至关重要的。努力工作,以便可以轻松地内联函数。使用链接时间优化。

请注意,除了可以极大地加快代码速度的移动语义外,编译器还可以进行(N)RVO-(命名)返回值优化,这实际上可以提高代码的效率。我已经测试了你的例子,在g++4.8中,你的第二个例子实际上可能更优化:

object3 a(x);
object2 b(a);
object1 c(b);
return c;

从我的实验来看,它会调用构造函数/析构函数8次(1 ctr+2个复制ctr+1个移动ctr+4个dtrs),而其他方法会调用它10次(1个ctr+5个移动ctrs)。但正如user2079303所评论的,move构造函数仍然应该优于copy构造函数,而且在本例中,所有调用都将内联,因此不会发生函数调用开销。

复制/移动省略实际上是"好像"规则的一个例外,这意味着有时你可能会对你的构造函数/析构函数(即使有副作用)没有被调用感到惊讶。

http://coliru.stacked-crooked.com/a/1ca7ebec0567e48f

(您可以使用-fno elide构造函数参数禁用(N)RVO)

#include <iostream>
#include <memory>
template<int S>
struct A {
A() { std::cout<<"A::A"<<std::endl; }    
template<int S2>
A(const A<S2>&) { std::cout<<"A::A&"<<std::endl; }
template<int S2>
A(const A<S2>&&) { std::cout<<"A::A&&"<<std::endl; }    
~A() { std::cout<<"~A::A"<<std::endl;}        
};
A<0> foo () {    
A<2> a; A<1> b(a); A<0> c(b); return c;   // calls dtor/ctor 8 times
//return A<0>(A<1>(A<2>()));  // calls dtor/ctor 10 times
}
int main()
{
A<0> a=foo();
return 0;
}