RVO强制编译错误失败 [英] RVO force compilation error on failure

查看:74
本文介绍了RVO强制编译错误失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

很多讨论在这里关于何时RVO可以做,但不太多,当它实际上做了。如前所述,RVO不能根据标准保证,但是否有一种方法可以确保RVO优化成功或相应的代码无法编译?



到目前为止,我部分成功地使得代码在RVO失败时出现链接错误。为此,我声明复制构造函数而不定义它们。显然,在非罕见的情况下,我需要实现一个或两个拷贝构造函数,即 x(x&&) x(x const&)



这里是我的第二个问题:为什么选择编译器来启用RVO当用户定义的复制构造函数存在时,但不存在时只有默认的复制构造函数?



第三个问题: RVO for plain data structure?



最后一个问题(承诺):你知道任何编译器使我的测试代码的行为, gcc和clang?



这里是gcc 4.6,gcc 4.8和clang 3.3的一些示例代码,该行为不依赖于一般优化或调试设置。当然选项 - no-elide-constructors 会做它的意思,即关闭RVO。

  #include< iostream> 
using namespace std;

struct x
{
x(){cout< 原始x地址<<这个<< endl; }
};
x make_x()
{
return x();
}

struct y
{
y(){cout< 原始y地址<<这个<< endl; }
//下面两个构造函数中的任何一个都将启用RVO,即使只有
//已声明但未定义。默认构造函数不会做!
y(y const& rhs);
y(y&& rhs);
};
y make_y()
{
return y();
}

int main()
{
auto x1 = make_x();
cout<< x地址的副本< & x1<< endl;
auto y1 = make_y();
cout<< y地址的副本< & y1<< endl;
}

输出:

 原始x address0x7fff8ef01dff 
x副本copy0x7fff8ef01e2e
原始y地址0x7fff8ef01e2f
副本y address0x7fff8ef01e2f

RVO似乎也不适用于纯数据结构:

  #include< iostream> 

using namespace std;

struct x
{
int a;
};

x make_x()
{
x tmp;
cout<< 原始x地址<< & tmp<< endl;
return tmp;
}

int main()
{
auto x1 = make_x();
cout<< x地址的副本< & x1<< endl;
}

输出:

  original x address0x7fffe7bb2320 
x copy0x7fffe7bb2350
的副本

EDIT:由于C ++ 11仍然很新,我忘记了 x make_x()最好使用统一初始化来编码:

  x make_x()
{
return {};
}


解决方案

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



首先,我禁用了 make_x()的内联否则我们不能区分RVO和内联。但是,我把其余的放在一个匿名命名空间,以便外部链接不会干扰任何其他编译器优化。 (如证据所示,外部链接可以防止内联,例如,谁知道什么...)我重写了输入输出,现在它使用 printf();否则生成的汇编代码将由于所有 iostream 内容而变得混乱。所以代码如下:

  #include< cstdio> 
using namespace std;

namespace {

struct x {
// int dummy [1024]
x(){printf(original x address%p\\\
,this); }
};

__attribute __((noinline))x make_x(){
return x();
}

} //命名空间

int main(){
auto x1 = make_x();
printf(x地址%p \\\
的副本,& x1);
}

我与一位同事分析了生成的汇编代码, gcc生成的汇编非常有限。今天晚些时候,我使用clang与 -S -emit-llvm 标志来生成 LLVM assembly ,我个人觉得比更好,更容易阅读X86装配/ GAS语法



我在C ++中重写了生成的程序集,如果 x 为空:

  #include< cstdio> 
using namespace std;

struct x {};

void make_x(){
x tmp;
printf(original x address%p \\\
,& tmp);
}

int main(){
x x1;
make_x();
printf(x地址%p \\\
的副本,& x1);
}

如果 x big( int dummy [1024]; member uncommented):

  #include< cstdio> 
using namespace std;

struct x {int dummy [1024]; };

void make_x(x * x1){

printf(original x address%p \\\
,x1);
}

int main(){
x x1;
make_x(& x1);
printf(x地址%p \\\
的副本,& x1);
}

结果是 make_x c $ c>只有打印一些有效的,唯一的地址,如果对象是空的。 make_x()有自由打印一些有效的地址指向它自己的堆栈如果对象是空的。也没有什么可以复制,没有从 make_x()返回。



对象更大(添加 int dummy [1024]; 成员),它被构造到位,RVO确实踢入,只有对象的地址传递给 make_x()



如果对象为空,编译器可以决定不将地址传递给 make_x()(这会浪费资源吗?:)),但让 make_x()从自己的堆栈构成一个唯一的有效地址。当这个优化发生是有点模糊,很难推理(这是你看到 y ),但它没有关系。



RVO看起来在那些重要的情况下会持续发生。而且,如我之前的混淆所示,即使整个 make_x()函数可以内联,因此没有返回值首先被优化。


Lots of discussions here about when RVO can be done but not much about when it is actually done. As stated may times, RVO can not be guaranteed according to the Standard but is there a way to guarantee that either RVO optimization succeeds or the corresponding code fails to compile?

So far I partially succeeded to make the code issue link errors when RVO fails. For this I declare the copy constructors without defining them. Obviously this is neither robust nor feasible in the non rare cases where I need to implement one or both copy constructors, i.e. x(x&&) and x(x const&).

This brings me to my second question: Why have the compiler writers chosen to enable RVO when user defined copy constructors are in place but not when only default copy constructors are present?

Third question: Is there some other way to enable RVO for plain data structures?

Last question (promise): Do you know any compiler that makes my test code behave other then I observed with gcc and clang?

Here is some example code for gcc 4.6, gcc 4.8 and clang 3.3 that shows the problem. The behavior does not depend on general optimization or debug settings. Of course option --no-elide-constructors does what it says, i.e. turns RVO off.

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

Output:

original x address0x7fff8ef01dff
copy of  x address0x7fff8ef01e2e
original y address0x7fff8ef01e2f
copy of  y address0x7fff8ef01e2f

RVO seems also not to work with plain data structures:

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

Output:

original x address0x7fffe7bb2320
copy of  x address0x7fffe7bb2350

EDIT: As C++11 is still very new, I forgot that x make_x() is best coded like this using uniform initialization:

x make_x ()
{
    return {};
}

解决方案

The problem is that the compiler is doing too much optimizations :)

First of all, I disabled the inlining of make_x() otherwise we cannot distinguish between RVO and inlining. However, I did put the rest into an anonymous namespace so that external linkage is not interfering with any other compiler optimizations. (As evidence shows, external linkage can prevent inlining for example, and who knows what else...) I rewrote the input-output, now it uses printf(); otherwise the generated assembly code would be cluttered due to all the iostream stuff. So the code:

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

I analyzed the generated assembly code with a colleague of mine as my understanding of the gcc generated assembly is very limited. Later today, I used clang with the -S -emit-llvm flags to generate LLVM assembly which I personally find much nicer and easier to read than the X86 Assembly/GAS Syntax. It didn't matter which compiler was used, the conclusions are the same.

I rewrote the generated assembly in C++, it roughly looks like this if x is empty:

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

If x is big (the int dummy[1024]; member uncommented):

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

It turns out that make_x() only has to print some valid, unique address if the object is empty. make_x() has the liberty to print some valid address pointing to its own stack if the object is empty. There is also nothing to be copied, there is nothing to return from make_x().

If you make the object bigger (add the int dummy[1024]; member for example), it gets constructed in place so RVO does kick in, and only the objects' address is passed to make_x() to be printed. No object gets copied, nothing gets moved.

If the object is empty, the compiler can decide not to pass an address to make_x() (What a waste of resources would that be? :) ) but let make_x() make up a unique, valid address from its own stack. When this optimization happens is somewhat fuzzy and hard to reason about (that is what you see with y) but it really doesn't matter.

RVO looks like to happen consistently in those cases where it matters. And, as my earlier confusion shows, even the whole make_x() function can get inlined so there is no return value to be optimized away in the first place.

这篇关于RVO强制编译错误失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆