C++ 返回值优化和副作用
返回值优化(RVO)是一种涉及复制省略的优化技术,它消除了在某些情况下为保存函数返回值而创建的临时对象。我总体上理解RVO的好处,但我有几个问题 本标准在第12.8节第32段(重点)中对其进行了如下说明 当满足某些条件时,即使对象的复制/移动构造函数和/或析构函数有副作用,也允许实现忽略类对象的复制/移动构造。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下被销毁的较晚时间 然后,它列出了实现何时可以执行此优化的一些标准C++ 返回值优化和副作用,c++,c++11,compiler-optimization,C++,C++11,Compiler Optimization,返回值优化(RVO)是一种涉及复制省略的优化技术,它消除了在某些情况下为保存函数返回值而创建的临时对象。我总体上理解RVO的好处,但我有几个问题 本标准在第12.8节第32段(重点)中对其进行了如下说明 当满足某些条件时,即使对象的复制/移动构造函数和/或析构函数有副作用,也允许实现忽略类对象的复制/移动构造。在这种情况下,实现将省略的复制/移动操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下被销毁的较晚时间 然后,它列出了实现何时可以执行此优化的一
关于这个潜在的优化,我有几个问题:
移动
):std::向量foo(int-bar){
标准:向量qux(bar,0);
返回标准::移动(quux);
}
编辑
我将此作为一个新问题发布,因为我提到的具体问题没有在其他相关问题中直接得到回答。它说得很清楚,不是吗?它允许省略有副作用的ctor。所以,你永远不应该有副作用,或者如果你坚持,你应该使用消除(N)RVO的技术。 至于第二个,我认为它禁止NRVO,因为
std::move
产生T&
,而不是T
,这将是NRVO(RVO)的候选,因为std::move
删除名称,NRVO需要它(感谢@DyP comment)
刚刚在MSVC上测试了以下代码:
#include <iostream>
class A
{
public:
A()
{
std::cout << "Ctor\n";
}
A(const A&)
{
std::cout << "Copy ctor\n";
}
A(A&&)
{
std::cout << "Move\n";
}
};
A foo()
{
A a;
return a;
}
int main()
{
A a = foo();
return 0;
}
#包括
甲级
{
公众:
()
{
STD::CUT< P>我强烈推荐阅读Stanely B. Lippman的“C++对象模型”中的详细信息和有关命名返回值优化如何工作的历史回顾。
例如,在第2.1章中,他谈到了命名回报值优化:
在诸如bar()之类的函数中,其中所有return语句都返回
相同的命名值,编译器本身可以进行优化
通过将结果参数替换为指定的返回值来调用函数
例如,给定bar()的原始定义:
__编译器将结果替换为xx:
void
bar( X &__result )
{
// default constructor invocation
// Pseudo C++ Code
__result.X::X();
// ... process in __result directly
return;
}
(……)
尽管NRV优化提供了显著的性能
然而,对于这种方法有几点批评。一是
因为优化是由编译器静默完成的,
它是否真的被执行并不总是很清楚(特别是
因为很少有编译器记录它的实现程度或
第二个是作为函数
变得越来越复杂,优化变得越来越困难
例如,在cfront中,只有在所有
命名的返回语句出现在函数的顶层。
引入一个带有return语句的嵌套本地块,然后
安静地关闭优化
我习惯于受约束的优化,这样它们就不能改变可观察的行为
这是正确的。作为一般规则,也就是所谓的“假设规则”,编译器可以在无法观察到更改的情况下更改代码
这一限制似乎不适用于RVO
是的。OP中引用的子句对“仿佛”规则给出了一个例外,允许省略复制构造,即使它有副作用。请注意,RVO只是复制省略的一种情况(C++11 12.8/31中的第一个项目符号)
我是否需要担心标准中提到的副作用
如果复制构造函数有副作用,当拷贝执行时会导致问题,那么你应该重新考虑设计。如果这不是你的代码,你应该考虑一个更好的选择。
作为程序员,我需要做什么(或不做什么)才能执行此优化
基本上,如果可能,返回一个与函数返回类型具有相同cv非限定类型的局部变量(或临时变量)。这允许RVO,但不强制执行它(编译器可能不执行RVO)
例如,以下是否禁止使用复制省略(由于移动):
看看这个,它太棒了
小心!如果返回类型为std::vector
以上最后一个代码的行为将与原始代码不同。(这是另一个故事。)
这可能是显而易见的,但如果您避免编写带有副作用的复制/移动构造函数(大多数都不需要它们)那么这个问题就完全没有意义了。即使是在简单的副作用案例中,如构建/破坏计数,它也应该是好的。唯一可能担心的是复杂的副作用,这是一种强烈的设计气味,可以重新测试代码
对我来说,这听起来是一种过早的优化。只需编写明显的、易于维护的代码,并让编译器优化。只有当分析显示某些区域表现不佳时,你是否应该考虑采用改变来提高性能。
可能的双重性
void
bar( X &__result )
{
// default constructor invocation
// Pseudo C++ Code
__result.X::X();
// ... process in __result directly
return;
}
// notice that I fixed the OP's example by adding <double>
std::vector<double> foo(int bar){
std::vector<double> quux(bar, 0);
return std::move(quux);
}
std::vector<double> foo(int bar){
std::vector<double> quux(bar,0);
return quux;
}
std::vector<double> foo(int bar){
return {bar, 0}; // <-- This doesn't work. Next line shows a workaround:
// return {bar, 0.0, std::vector<double>::allocator_type{}};
}