为什么不执行“部分RVO”?

时间:2019-05-29 09:44:25

标签: c++ optimization rvo

请看一下这个愚蠢的功能,该功能只应说明问题并简化实际代码:

struct A;

A create(bool first){
        A f(21), s(42);
        if(first)
           return f;
        else
           return s;
}

我知道,由于不清楚在编译过程中将返回哪个对象,因此我们不能期望总是执行返回值优化(RVO)。

但是,人们可能希望在50%的情况下执行RVO(由于缺乏更多信息,假设true / false的分配均匀):只需确定哪种情况下RVO(应该执行first==truefirst==false)并将其应用于此参数值,并接受在另一种情况下必须调用复制构造函数的方法。

但是,并非所有可以使用的编译器都遇到这种“部分RVO”的情况(请参见gccclangMSVC的现场介绍)-在两种情况下(即first==truefirst==false)使用了复制构造函数,并且没有省略。

是否存在某种导致上述情况下的“部分RVO”无效的情况,或者这是所有编译器都错过优化的不太可能的情况?


完整程序:

#include <iostream>

struct A{
    int val;
    A(int val_):val(val_){}
    A(const A&o):val(o.val){
        std::cout<<"copying: "<<val<<"\n";
    }
};

A create(bool first){
        A f(21), s(42);
        if(first)
           return f;
        else
           return s;
}

int main(){
    std::cout<<"With true: ";
    create(true);
    std::cout<<"With false: ";
    create(false);
}

2 个答案:

答案 0 :(得分:10)

让我们考虑一下,如果对f执行RVO,会发生什么情况,这意味着它直接在返回值中构造。如果返回first==truef,很好,不需要副本。但是,如果返回first==false,而返回s,则程序将在{{1}的析构函数之前的{em> 之前,将结构s复制到f的顶部1}}已运行。然后,f的析构函数将运行,现在返回值是一个已经被销毁的无效对象!

如果对f执行了RVO,则使用相同的参数,只是现在问题发生在s上。

无论选择哪种方法,都可以在50%的情况下避免复制,而在另50%的情况下获得不确定的行为!这不是理想的优化!

为了完成这项工作,必须更改局部变量的销毁顺序,以便在{em> 将first==true复制到该内存位置之前销毁f(反之亦然),这是一个非常冒险的事情。销毁顺序是C ++的一项基本属性,不应该被搞砸,否则您将破坏RAII,并且谁知道还有多少其他假设。

答案 1 :(得分:1)

除了对此感兴趣的阅读乔纳森·韦克利(Jonathan Wakely)的答案,我的看法是,人们总是可以为要返回的对象定义一个移动构造函数。如果无论出于何种原因都不能应用RVO,那么这将比复制构造方法更受青睐。

std::vector这样的东西定义了这样的构造函数,因此您可以免费获得它。