请看一下这个愚蠢的功能,该功能只应说明问题并简化实际代码:
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==true
或first==false
)并将其应用于此参数值,并接受在另一种情况下必须调用复制构造函数的方法。
但是,并非所有可以使用的编译器都遇到这种“部分RVO”的情况(请参见gcc,clang和MSVC的现场介绍)-在两种情况下(即first==true
或first==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);
}
答案 0 :(得分:10)
让我们考虑一下,如果对f
执行RVO,会发生什么情况,这意味着它直接在返回值中构造。如果返回first==true
和f
,很好,不需要副本。但是,如果返回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
这样的东西定义了这样的构造函数,因此您可以免费获得它。