失败时RVO强制编译错误

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就是一个例子.请参阅此示例,其中标准实际执行了优化.

Ali*_*Ali 5

问题是编译器做了太多的优化:)

首先,我禁用了内联,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()函数也可以内联,因此首先没有返回值进行优化.