为什么三元运算符阻止返回值优化
Why does the ternary operator prevent Return-Value Optimization?
为什么三元运算符阻止MSVC中的返回值优化(RVO)?考虑以下完整的示例程序:
#include <iostream>
struct Example
{
Example(int) {}
Example(Example const &) { std::cout << "copyn"; }
};
Example FunctionUsingIf(int i)
{
if (i == 1)
return Example(1);
else
return Example(2);
}
Example FunctionUsingTernaryOperator(int i)
{
return (i == 1) ? Example(1) : Example(2);
}
int main()
{
std::cout << "using if:n";
Example obj1 = FunctionUsingIf(0);
std::cout << "using ternary operator:n";
Example obj2 = FunctionUsingTernaryOperator(0);
}
用VC 2013这样编译:cl /nologo /EHsc /Za /W4 /O2 stackoverflow.cpp
输出:
using if:
using ternary operator:
copy
很明显,三元运算符在某种程度上阻止了RVO。为什么?为什么编译器不够聪明,不能看到使用三元运算符的函数与使用if语句的函数做同样的事情,并相应地进行优化?
查看程序输出,在我看来,编译器确实在这两种情况下都删除了,为什么?
因为,如果没有激活elide,正确的输出将是:
- 在函数return处构造示例对象
- 将其复制到临时
- 将临时复制到main函数中定义的对象
所以,我希望在我的屏幕上至少有2个"副本"输出。事实上,如果我执行你的程序,用g++和-fno-elide构造函数编译,我会从每个函数中得到2条复制消息。
有趣的是,如果我对clang也这样做,当调用函数FunctionUsingTernaryOperator(0);
时,我得到了3条"复制"消息,我想,这是由于编译器如何实现三进制。我猜它正在生成一个临时来解决三元运算符,并将这个临时复制到return语句中。
此相关问题包含答案。
标准规定何时允许在退货声明中省略复制或移动:(12.8.31)
- 在具有类返回类型的函数中的返回语句中,当表达式是非易失性自动对象(而不是函数或catch子句参数)的名称,该对象的类型与函数返回类型相同,则可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作
- 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv不合格类型的类对象时,可以通过将临时对象直接构建到被省略的复制/移动的目标中来省略复制/移动操作
所以基本上复制省略只发生在以下情况下:
- 返回一个命名对象
- 返回临时对象
如果表达式不是命名对象或临时对象,则返回复制。
一些有趣的行为:
return (name);
不阻止复制省略(请参阅此问题)return true?name:name;
应该防止复制省略,但gcc 4.6至少在这一点上是错误的(参见这个问题)
编辑:
我在上面留下了我的原始答案,但克里斯蒂安·哈克尔的评论是正确的,它没有回答这个问题。
就规则而言,示例中的三元运算符产生一个临时对象,因此12.8.31允许消除复制/移动。所以从C++语言的角度来看编译器完全可以在从FunctionUsingTernaryOperator返回时消除副本
现在显然省略还没有完成。我想唯一的原因是Visual Studio编译器团队还并没有实现它。因为理论上他们可以,也许在未来的发行版中他们会。
我可以看到,它违反了关于RVO的一条一般规则——返回对象(应该)定义在一个位置。
下面的代码段满足规则:
Example e;
e = (i == 1)? Example{1} : Example{2};
return e;
但在下面这样的原始表达式中,根据MSVC在两个不同的位置定义了两个Example
对象:
return (i == 1) ? Example(1) : Example(2);
虽然这两个片段之间的转换对人类来说是微不足道的,但我可以想象,如果没有专门的实现,它不会在编译器中自动发生。换句话说,这是一个从技术上讲可以RVO的角落案例,但开发人员没有意识到。
- 返回值优化:显式移动还是隐式
- 使用 std::p air 进行返回值优化
- C++ 特征图3.5,特征图不使用命名返回值优化?
- 我是否正确测试了返回值优化?
- 使用std::optional时的命名返回值优化
- 找不到使保证返回值优化工作的方法
- 局部堆栈变量成员的返回值优化
- 在没有返回值优化的情况下将两个对象加在一起时,将创建多少个临时对象
- 为什么三元运算符阻止返回值优化
- 实现move构造函数如何影响返回值优化
- 我如何确定将进行返回值优化
- 返回值优化并复制C中的ELINION
- 为什么我在此代码中没有得到返回值优化?
- 了解工厂方法和静态变量赋值的返回值优化 (Visual Studio)
- 返回值优化是否需要声明一个复制构造函数
- 从具有移动语义或返回值优化的函数返回值,但不返回复制构造函数
- RVO(返回值优化)无法解释这个谜团
- 元组/领带的返回值优化
- 无法禁用 std::string 的返回值优化
- 返回值优化和副作用