9

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

到目前为止,当 RVO 失败时,我部分成功地使代码问题链接错误。为此,我声明了复制构造函数而不定义它们。显然,在我需要实现一个或两个复制构造函数(即x(x&&)x(x const&).

这就引出了我的第二个问题:为什么编译器编写者选择在用户定义的复制构造函数就位时启用 RVO,而不是在只有默认复制构造函数存在时?

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

最后一个问题(承诺):你知道任何编译器使我的测试代码表现得比我用 gcc 和 clang 观察到的吗?

以下是显示问题的 gcc 4.6、gcc 4.8 和 clang 3.3 的一些示例代码。该行为不依赖于一般优化或调试设置。当然 option--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就是一个例子。请参阅此示例,其中优化实际上是由标准强制执行的。

4

3 回答 3

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

我和我的一个同事分析了生成的汇编代码,因为我对 gcc 生成的汇编的理解非常有限。今天晚些时候,我使用带有-S -emit-llvm标志的 clang 来生成LLVM 程序集,我个人认为它比X86 Assembly/GAS Syntax更好、更容易阅读。使用哪个编译器无关紧要,结论是相同的。

x我用 C++ 重写了生成的程序集,如果为空,它大致如下所示:

#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()函数都可以内联,因此一开始就没有需要优化的返回值。

于 2013-10-09T11:56:38.780 回答
2
  1. 我不相信有任何方法可以做出这样的保证。RVO 是一种优化,因此编译器可能会在特定情况下确定使用它实际上是一种反优化并选择不这样做。

  2. 我假设您指的是您的第一个代码段。ideone.com在 32 位编译中,即使根本没有启用优化,我也无法在 g++ 4.4、4.5 或 4.8(通过)上重现您的断言。在 64 位编译中,我可以重现您的无 RVO 行为。这闻起来像 g++ 中的 64 位代码生成错误。

  3. 如果实际上我观察到的(2)是一个错误,那么一旦修复了错误,它就会起作用。

  4. 我可以确认,即使在 32 位编译中,Sun CC 也不会RVO 您的具体示例。

但是,我确实想知道您打印出地址的自省代码是否会导致编译器禁止优化(例如,它可能需要禁止优化以防止可能的别名问题)。

于 2013-10-09T15:17:10.880 回答
0

为什么编译器编写者选择在用户定义的复制构造函数就位时启用 RVO,但在仅存在默认复制构造函数时不选择启用 RVO?

因为标准是这样说的:

C++14、12.8/31:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。

C++14、12.8/32

当满足或将满足复制操作的省略标准时,除了源对象是函数参数的事实,并且要复制的对象由左值指定时,选择复制的构造函数的重载决策是首先执行好像对象是由右值指定的。如果重载决议失败,或者如果所选构造函数的第一个参数的类型不是对对象类型的右值引用(可能是 cv 限定的),则再次执行重载决议,将对象视为左值。[注意:无论是否会发生复制省略,都必须执行此两阶段重载解析。它确定如果不执行省略则要调用的构造函数,并且即使调用被省略,所选构造函数也必须是可访问的。——尾注]

您必须记住,RVO(和其他复制省略)是可选的。

想象一下,由于 RVO 启动,删除了复制/移动构造函数/赋值的代码在您的编译器上进行编译。然后,您将完美编译的代码移到另一个编译器中,在该编译器中它合法地无法编译。这是不可接受的。

这意味着即使编译器出于某种原因决定不进行 RVO 优化,代码也必须始终有效。

于 2015-05-19T15:36:37.710 回答