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

时间:2014-02-27 19:06:29

标签: c++

为什么三元运算符会阻止MSVC中的返回值优化(RVO)?请考虑以下完整的示例程序:

#include <iostream>

struct Example
{
    Example(int) {}
    Example(Example const &) { std::cout << "copy\n"; }
};

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编译:{{1​​}}

输出:

cl /nologo /EHsc /Za /W4 /O2 stackoverflow.cpp

显然,三元运算符以某种方式阻止了RVO。为什么?为什么编译器不够聪明,看不到使用三元运算符的函数与使用if语句的函数做同样的事情,并相应地进行优化?

3 个答案:

答案 0 :(得分:4)

看看程序输出,在我看来,确实,编译器在这两种情况下都没有,为什么?

因为,如果没有激活elide,正确的输出将是:

  1. 在函数return;
  2. 构造示例对象
  3. 将其复制到临时;
  4. 将临时文件复制到main函数中定义的对象。
  5. 所以,我希望,至少2&#34; copy&#34;在我的屏幕输出。实际上,如果我执行你的程序,使用g ++编译,使用-fno-elide-constructor,我从每个函数中获得了2条复制消息。

    有趣的是,如果我对clang做同样的事情,我得到3&#34;复制&#34;调用函数FunctionUsingTernaryOperator(0);时的消息,我想,这是由于编译器如何实现三元组。我想它正在生成一个临时解决三元运算符并将此临时值复制到return语句。

答案 1 :(得分:2)

related question包含答案。

标准说明在返回声明中允许复制或移动省略时:(12.8.31)

  • 在具有类返回类型的函数的return语句中,当表达式是具有与函数返回类型相同的cvunqualified类型的非易失性自动对象(函数或catch子句参数除外)的名称时,通过将自动对象直接构造为函数的返回值
  • ,可以省略复制/移动操作
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过直接构造临时对象来省略复制/移动操作进入省略的复制/移动的目标

因此,基本上只有在以下情况下才会出现复制省略:

  1. 返回一个命名对象。
  2. 返回临时对象
  3. 如果您的表达式不是命名对象或临时对象,则可以回退复制。

    一些有趣的行为:

    • return (name);不会阻止复制省略(请参阅this question
    • return true?name:name;应该防止复制,但gcc 4.6至少在这个问题上是错误的(参见this question

    修改

    我在上面留下了原来的答案,但Christian Hackl的评论是正确的,它没有回答这个问题。

    就规则而言,示例中的三元运算符会产生一个临时对象,因此12.8.31允许复制/移动。所以从C ++语言的角度来看。 从FunctionUsingTernaryOperator返回时,完全允许编译器忽略副本。

    现在很明显,没有完成任务。我想唯一的原因是Visual Studio Compiler团队还没有实现它。而且因为从理论上讲,他们可以在将来的版本中使用它们。

答案 2 :(得分:0)

我看到它违反了有关RVO的一条一般规则-(应该)在单个位置定义返回对象。

下面的代码片段满足规则:

e

但是在如下所示的原始表达式中,根据MSVC在两个不同的位置定义了两个Example e; e = (i == 1)? Example{1} : Example{2}; return e; 对象:

Example

尽管两个片段之间的转换对于人类来说是微不足道的,但我可以想象,如果没有专门的实现,它不会自动在编译器中发生。换句话说,这是一个极端案例,从技术上讲可以RVO,但是开发人员没有意识到。