为什么 "partial RVO" 没有执行?

Why is "partial RVO" not performed?

请看看这个愚蠢的函数,它应该只是说明问题和简化实际代码:

struct A;

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

我明白,因为在编译期间不清楚哪个对象将被 returned,我们不能指望总是执行 return 值优化 (RVO)。

然而,人们可能期望在 50% 的情况下执行 RVO(由于缺乏进一步的信息,假设 true/false 均匀分布):只需决定哪种情况应该执行 RVO(first==truefirst==false)并将其应用于此参数值,接受在其他情况下必须调用复制构造函数。

然而,这 "partial RVO" 并不是所有我能接触到的编译器的情况(见 gcc, clang and MSVC 的 live)——在这两种情况下(即 first==truefirst==false) 复制构造函数被使用并且没有被省略。

是否有什么东西使上述情况下的 "partial 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);
}

让我们考虑如果为 f 完成 RVO 会发生什么,这意味着它直接在 return 值中构造。如果 first==truef 得到 returned,很好,不需要副本。但是如果 first==falses 得到 returned,所以程序将在 f 之前复制构造 s 34=] f 的析构函数有 运行。然后在这之后,f的析构函数将运行,现在return值是一个已经被销毁的无效对象!

如果为 s 完成 RVO,则适用相同的论点,只是现在问题发生在 first==true

无论您选择哪一个,您都可以在 50% 的情况下避免复制,而在另外 50% 的情况下会出现未定义的行为!这不是一个理想的优化!

为了完成这项工作,必须更改局部变量的销毁顺序,以便 f 复制 之前销毁 s进入该内存位置(反之亦然),这是一件 非常 冒险的事情。销毁的顺序是 C++ 的基本 属性,不应该乱动,否则你会破坏 RAII,谁知道还有多少其他假设。

除了感兴趣地阅读 Jonathan Wakely 的回答之外,我对此的看法是,人们总是可以为返回的对象定义一个移动构造函数。如果出于任何原因无法应用 RVO,这将比复制构造函数更受青睐,并且在我看来是一个很好的解决方案。

std::vector这样的东西定义了这样一个构造函数,所以你可以免费得到它。