为什么 "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==true
或 first==false
)并将其应用于此参数值,接受在其他情况下必须调用复制构造函数。
然而,这 "partial RVO" 并不是所有我能接触到的编译器的情况(见 gcc, clang and MSVC 的 live)——在这两种情况下(即 first==true
或 first==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==true
和 f
得到 returned,很好,不需要副本。但是如果 first==false
则 s
得到 returned,所以程序将在 f
之前复制构造 s
34=] f
的析构函数有 运行。然后在这之后,f
的析构函数将运行,现在return值是一个已经被销毁的无效对象!
如果为 s
完成 RVO,则适用相同的论点,只是现在问题发生在 first==true
。
无论您选择哪一个,您都可以在 50% 的情况下避免复制,而在另外 50% 的情况下会出现未定义的行为!这不是一个理想的优化!
为了完成这项工作,必须更改局部变量的销毁顺序,以便 f
在 复制 之前销毁 s
进入该内存位置(反之亦然),这是一件 非常 冒险的事情。销毁的顺序是 C++ 的基本 属性,不应该乱动,否则你会破坏 RAII,谁知道还有多少其他假设。
除了感兴趣地阅读 Jonathan Wakely 的回答之外,我对此的看法是,人们总是可以为返回的对象定义一个移动构造函数。如果出于任何原因无法应用 RVO,这将比复制构造函数更受青睐,并且在我看来是一个很好的解决方案。
像std::vector
这样的东西定义了这样一个构造函数,所以你可以免费得到它。
请看看这个愚蠢的函数,它应该只是说明问题和简化实际代码:
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==true
或 first==false
)并将其应用于此参数值,接受在其他情况下必须调用复制构造函数。
然而,这 "partial RVO" 并不是所有我能接触到的编译器的情况(见 gcc, clang and MSVC 的 live)——在这两种情况下(即 first==true
或 first==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==true
和 f
得到 returned,很好,不需要副本。但是如果 first==false
则 s
得到 returned,所以程序将在 f
之前复制构造 s
34=] f
的析构函数有 运行。然后在这之后,f
的析构函数将运行,现在return值是一个已经被销毁的无效对象!
如果为 s
完成 RVO,则适用相同的论点,只是现在问题发生在 first==true
。
无论您选择哪一个,您都可以在 50% 的情况下避免复制,而在另外 50% 的情况下会出现未定义的行为!这不是一个理想的优化!
为了完成这项工作,必须更改局部变量的销毁顺序,以便 f
在 复制 之前销毁 s
进入该内存位置(反之亦然),这是一件 非常 冒险的事情。销毁的顺序是 C++ 的基本 属性,不应该乱动,否则你会破坏 RAII,谁知道还有多少其他假设。
除了感兴趣地阅读 Jonathan Wakely 的回答之外,我对此的看法是,人们总是可以为返回的对象定义一个移动构造函数。如果出于任何原因无法应用 RVO,这将比复制构造函数更受青睐,并且在我看来是一个很好的解决方案。
像std::vector
这样的东西定义了这样一个构造函数,所以你可以免费得到它。