这里有很多关于何时可以完成RVO的讨论,但是没有多少关于何时实际完成的讨论。如可能有时所述,不能根据标准保证RVO,但是是否有办法保证RVO优化成功或相应的代码无法编译?

到目前为止,当RVO失败时,我部分成功地使代码发出了链接错误。为此,我声明了复制构造函数而不定义它们。显然,在我需要实现一个或两个拷贝构造函数(即x(x&&)x(x const&))的极少数情况下,这既不健壮也不可行。

这使我想到第二个问题:为什么在使用用户定义的拷贝构造函数时选择编译器编写器以启用RVO,而在仅使用默认拷贝构造函数时却选择启用RVO?

第三个问题:是否有其他方法可以为纯数据结构启用RVO?

最后一个问题( promise ):您是否知道有任何编译器会使我的测试代码行为与我在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;
}

输出:
original x address0x7fff8ef01dff
copy of  x address0x7fff8ef01e2e
original y address0x7fff8ef01e2f
copy of  y address0x7fff8ef01e2f

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;
}

输出:
original x address0x7fffe7bb2320
copy of  x address0x7fffe7bb2350

更新:请注意,某些优化很容易与RVO混淆。诸如make_x之类的构造方法助手就是一个示例。请参阅this example,其中该优化实际上是由标准实现的。

最佳答案

问题是编译器进行了太多优化:)

首先,我禁用了make_x()的内联,否则我们无法区分RVO和内联。但是,我确实将其余部分放在了一个匿名 namespace 中,以便外部链接不会干扰任何其他编译器优化。 (如证据所示,外部链接可以防止内联,例如,谁知道呢?)我重写了输入输出,现在使用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);
}

我与我的一位同事分析了生成的程序集代码,因为我对gcc生成的程序集的了解非常有限。今天晚些时候,我将clang与-S -emit-llvm标志一起使用来生成LLVM assembly,与X86 Assembly/GAS Syntax相比,我个人觉得它更好,更易于阅读。使用哪个编译器都没关系,结论是相同的。

我用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);
}

如果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);
}

事实证明,如果对象为空,make_x()仅需打印一些有效的唯一地址。如果对象为空,make_x()可以打印一些指向其自身堆栈的有效地址。也没有要复制的内容,没有要从make_x()返回的内容。

如果使对象更大(例如,添加int dummy[1024];成员),则会在适当的位置构造它,因此RVO确实会插入,并且仅将对象的地址传递到make_x()进行打印。没有对象被复制,没有任何东西被移动。

如果对象为空,则编译器可以决定不将地址传递给make_x()(这会浪费资源吗?:)),而是让make_x()从其自己的堆栈中构成唯一的有效地址。当进行这种优化时,有些模糊并且难以推理(这就是您在y上看到的结果),但实际上并不重要。

在重要的情况下,RVO似乎会持续发生。而且,正如我之前的困惑所表明的那样,即使是整个make_x()函数也可以内联,因此一开始就没有要优化的返回值。

关于c++ - RVO强制编译失败,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/19262009/

10-11 18:17