Pat*_*erg 9 c++ inline compiler-optimization copy-elision c++11
这里有很多关于何时可以进行RVO的讨论,但关于什么时候实际完成的情况并不多.如上所述,根据标准,无法保证RVO,但有没有办法保证RVO优化成功或相应的代码无法编译?
到目前为止,当RVO失败时,我部分成功地使代码发出链接错误.为此,我声明了复制构造函数而没有定义它们.显然,在我需要实现一个或两个拷贝构造函数的非罕见情况下,这既不强大也不可行,即x(x&&)和x(x const&).
这让我想到了第二个问题:为什么选择编译器编写器来在用户定义的复制构造函数到位时启用RVO,而不是仅在存在默认复制构造函数时?
第三个问题:是否有其他方法可以为普通数据结构启用RVO?
最后一个问题(承诺):您是否知道任何编译器使我的测试代码表现得与我用gcc和clang观察到的不同?
下面是gcc 4.6,gcc 4.8和clang 3.3的一些示例代码,用于显示问题.该行为不依赖于常规优化或调试设置.当然选项--no-elide-constructors会按照它说的做,即关闭RVO.
#include <iostream>
using namespace std;
struct x
{
x () { cout << "original x address" << this << endl; }
};
x make_x ()
{
return x();
}
struct y
{
y () { cout << "original y address" << this << endl; }
// Any of the next two constructors will enable RVO even if only
// declared but not defined. Default constructors will not do!
y(y const & rhs);
y(y && rhs);
};
y make_y ()
{
return y();
}
int main ()
{
auto x1 = make_x();
cout << "copy of x address" << &x1 << endl;
auto y1 = make_y();
cout << "copy of y address" << &y1 << endl;
}
Run Code Online (Sandbox Code Playgroud)
输出:
original x address0x7fff8ef01dff
copy of x address0x7fff8ef01e2e
original y address0x7fff8ef01e2f
copy of y address0x7fff8ef01e2f
Run Code Online (Sandbox Code Playgroud)
RVO似乎也不适用于普通数据结构:
#include <iostream>
using namespace std;
struct x
{
int a;
};
x make_x ()
{
x tmp;
cout << "original x address" << &tmp << endl;
return tmp;
}
int main ()
{
auto x1 = make_x();
cout << "copy of x address" << &x1 << endl;
}
Run Code Online (Sandbox Code Playgroud)
输出:
original x address0x7fffe7bb2320
copy of x address0x7fffe7bb2350
Run Code Online (Sandbox Code Playgroud)
更新:请注意,一些优化很容易与RVO混淆.构造函数助手make_x就是一个例子.请参阅此示例,其中标准实际执行了优化.
问题是编译器做了太多的优化:)
首先,我禁用了内联,make_x()否则我们无法区分RVO和内联.但是,我确实将其余部分放入匿名命名空间中,以便外部链接不会干扰任何其他编译器优化.(正如证据所示,外部链接可以阻止内联,例如,谁知道还有什么...)我重写了输入输出,现在它使用printf(); 否则,由于所有的iostream东西,生成的汇编代码会混乱.所以代码:
#include <cstdio>
using namespace std;
namespace {
struct x {
//int dummy[1024];
x() { printf("original x address %p\n", this); }
};
__attribute__((noinline)) x make_x() {
return x();
}
} // namespace
int main() {
auto x1 = make_x();
printf("copy of x address %p\n", &x1);
}
Run Code Online (Sandbox Code Playgroud)
我与我的一位同事分析了生成的汇编代码,因为我对gcc生成的汇编的理解非常有限.今天晚些时候,我使用clang with the -S -emit-llvmflags来生成LLVM程序集,我个人觉得它比X86汇编/ GAS语法更好更容易阅读.使用哪个编译器并不重要,结论是一样的.
我在C++中重写了生成的程序集,如果x为空则大致如下所示:
#include <cstdio>
using namespace std;
struct x { };
void make_x() {
x tmp;
printf("original x address %p\n", &tmp);
}
int main() {
x x1;
make_x();
printf("copy of x address %p\n", &x1);
}
Run Code Online (Sandbox Code Playgroud)
如果x很大(int dummy[1024];成员未注释):
#include <cstdio>
using namespace std;
struct x { int dummy[1024]; };
void make_x(x* x1) {
printf("original x address %p\n", x1);
}
int main() {
x x1;
make_x(&x1);
printf("copy of x address %p\n", &x1);
}
Run Code Online (Sandbox Code Playgroud)
事实证明,make_x()如果对象为空,只需要打印一些有效的唯一地址.make_x()如果对象为空,则可以自由打印指向其自己堆栈的有效地址.没有什么可以复制的,没有什么可以归还的make_x().
如果你使对象更大(int dummy[1024];例如添加成员),它就会被构造到位,因此RVO会启动,并且只传递对象的地址make_x()以进行打印.没有对象被复制,没有任何东西被移动.
如果对象是空的,编译器可以决定不传递地址make_x()(这会浪费多少资源?))但是让它make_x()从自己的堆栈中组成一个唯一的,有效的地址.当这种优化发生时有点模糊,难以推理(这就是你所看到的y),但它确实无关紧要.
在重要的情况下,RVO似乎始终如一.并且,正如我之前的混淆所示,即使整个make_x()函数也可以内联,因此首先没有返回值进行优化.
| 归档时间: |
|
| 查看次数: |
636 次 |
| 最近记录: |