为什么三元运算符阻止返回值优化

Why does the ternary operator prevent Return-Value Optimization?

本文关键字:返回值 优化 运算符 三元 为什么      更新时间:2023-10-16

为什么三元运算符阻止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,正确的输出将是:

  1. 在函数return处构造示例对象
  2. 将其复制到临时
  3. 将临时复制到main函数中定义的对象

所以,我希望在我的屏幕上至少有2个"副本"输出。事实上,如果我执行你的程序,用g++和-fno-elide构造函数编译,我会从每个函数中得到2条复制消息。

有趣的是,如果我对clang也这样做,当调用函数FunctionUsingTernaryOperator(0);时,我得到了3条"复制"消息,我想,这是由于编译器如何实现三进制。我猜它正在生成一个临时来解决三元运算符,并将这个临时复制到return语句中。

此相关问题包含答案。

标准规定何时允许在退货声明中省略复制或移动:(12.8.31)

  • 在具有类返回类型的函数中的返回语句中,当表达式是非易失性自动对象(而不是函数或catch子句参数)的名称,该对象的类型与函数返回类型相同,则可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作
  • 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv不合格类型的类对象时,可以通过将临时对象直接构建到被省略的复制/移动的目标中来省略复制/移动操作

所以基本上复制省略只发生在以下情况下:

  1. 返回一个命名对象
  2. 返回临时对象

如果表达式不是命名对象或临时对象,则返回复制。

一些有趣的行为:

  • 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的角落案例,但开发人员没有意识到。