我试图解释为什么一个相当不错的C ++ 11编译器(clang)没有优化这段代码,并想知道这里是否有人有意见。
#include <iostream>
#define SLOW
struct A {
A() {}
~A() { std::cout << "A d'tor\n"; }
A(const A&) { std::cout << "A copy\n"; }
A(A&&) { std::cout << "A move\n"; }
A &operator =(A) { std::cout << "A copy assignment\n"; return *this; }
};
struct B {
// Using move on a sink.
// Nice talk at Going Native 2013 by Sean Parent.
B(A foo) : a_(std::move(foo)) {}
A a_;
};
A MakeA() {
return A();
}
B MakeB() {
// The key bits are in here
#ifdef SLOW
A a(MakeA());
return B(a);
#else
return B(MakeA());
#endif
}
int main() {
std::cout << "Hello World!\n";
B obj = MakeB();
std::cout << &obj << "\n";
return 0;
}
如果我使用#define SLOW
注释掉并使用-s
进行优化来运行此操作
Hello World!
A move
A d'tor
0x7fff5fbff9f0
A d'tor
这是预期的。
如果我在启用#define SLOW
并使用-s
进行优化的情况下运行此操作,我会:
Hello World!
A copy
A move
A d'tor
A d'tor
0x7fff5fbff9e8
A d'tor
这显然不是很好。所以问题是:
为什么我没有在“SLOW”案例中看到应用NRVO优化?我知道编译器不需要应用NRVO,但这似乎是一个常见的简单情况。
总的来说,我尝试鼓励“SLOW”风格的代码,因为我觉得它更容易调试。
答案 0 :(得分:13)
简单的答案是:因为在这种情况下不允许应用复制省略。仅在极少数和特定情况下才允许编译器应用复制省略。该标准的引用是12.8 [class.copy]第31段:
......在下列情况下(允许合并以消除多份副本),允许复制/移动操作(称为复制省略)的省略:
- 在具有类返回类型的函数的return语句中,当表达式是非易失性自动对象的名称(函数或catch子句参数除外)时,具有与函数返回相同的cv非限定类型通过类型,可以通过将自动对象直接构造到函数的返回值
中来省略复制/移动操作- [...]
显然,B(a)
的类型不是A
,即不允许复制省略。同一段中的其他项目符号指的是throw
表达式,从临时中删除副本以及异常声明等内容。这些都不适用。
答案 1 :(得分:2)
您在慢速路径中看到的副本不是由于缺少RVO而是由于这个原因造成的 在B(MakeA())中,“MakeA()”是一个右值,但在B(a)中,“a”是一个左值。
要明确这一点,让我们修改慢速路径以指示MakeA()完成的位置:
#ifdef SLOW
A a(MakeA());
std::cout << "---- after call \n";
return B(a);
#else
输出结果为:
Hello World!
---- after call
A copy
A move
A d'tor
A d'tor
0x7fff5a831b28
A d'tor
表明没有在
中完成复制A a(MakeA());
因此,RVO确实发生了。
删除所有副本的修复程序是:
return B(std::move(a));