c++ - RVO 在失败时强制编译错误

标签 c++ c++11 inline compiler-optimization copy-elision

这里有很多关于何时可以完成 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/

相关文章:

c++ - 各种类型的容器 - C++

c++ - 在 makefile 中添加 c++11 以消除错误 to_string is not declared in this scope

C++:函数指针作为模板参数而不是仿函数

c++ - 如何使用c++实时数据增加gnuplot的绘图频率?

c++ - 如何检查数组是否等于某组值

matlab - 脚本中的递归内联匿名函数

c++ - 使用 clang 时在 gdb 中评估 libc++ 的方法

c++ - 在哪种情况下,我们在 C 中只定义枚举中的一个成员?

c++ - 散列 std::vector 独立于项目顺序

javascript - Firebug 无法选择脚本