C ++ 17引入了哪些评估顺序保证?

时间:2016-07-21 10:21:53

标签: c++ c++17 operator-precedence

C++17 evaluation order guarantees (P0145)投票对典型C ++代码的影响是什么?

之类的内容有什么变化
i=1;
f(i++, i)

std::cout << f() << f() << f() ;

f(g(),h(),j());

3 个答案:

答案 0 :(得分:58)

到目前为止评估订单未指定的一些常见情况已指定且对C++17有效。现在,未指定某些未定义的行为。

  

这样的事情
i=1;
f(i++, i)

未定义但现在未指定。具体而言,未指定的是f的每个参数相对于其他参数的评估顺序。可能会在i++之前评估i,反之亦然。实际上,尽管属于同一个编译器,它可能会以不同的顺序评估第二个调用。

但是,在执行任何其他参数之前,每个参数的评估都是 required 以完全执行,并带有所有副作用。所以你可能得到f(1, 1)(第一个被评估的参数)或f(1, 2)(第一个被评估的参数)。但是你永远不会得到f(2, 2)或其他任何性质的东西。

std::cout << f() << f() << f() ;

未指定,但将与运营商优先级兼容,以便f的第一次评估将首先出现在流中。 (以下示例)。

f(g(),h(),j());

仍然有未指定的g,h,j的评估顺序。请注意,对于getf()(g(),h(),j()),规则规定getf()将在g,h,j之前进行评估。

另请注意提案文字中的以下示例:

 std::string s = "but I have heard it works even if you don't believe in it" 
 s.replace(0, 4, "").replace(s.find("even"), 4, "only")
  .replace(s.find(" don't"), 6, "");

该示例来自 C ++编程语言,第4版,Stroustrup ,并且曾经是未指定的行为,但使用C ++ 17它将按预期工作。可恢复功能存在类似问题(.then( . . . ))。

作为另一个例子,请考虑以下事项:

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

struct Speaker{
    int i =0;
    Speaker(std::vector<std::string> words) :words(words) {}
    std::vector<std::string> words;
    std::string operator()(){
        assert(words.size()>0);
        if(i==words.size()) i=0;
        // pre- C++17 version:
        auto word = words[i] + (i+1==words.size()?"\n":",");
        ++i;
        return word;
        // Still not possible with C++17:
        // return words[i++] + (i==words.size()?"\n":",");

    }   
};

int main() {
    auto spk = Speaker{{"All", "Work", "and", "no", "play"}};
    std::cout << spk() << spk() << spk() << spk() << spk() ;
}

使用C ++ 14之前我们可能(并且将会)获得诸如

之类的结果
play
no,and,Work,All,

而不是

All,work,and,no,play

请注意,上述内容实际上与

相同
(((((std::cout << spk()) << spk()) << spk()) << spk()) << spk()) ;

但是,在C ++ 17之前,并不能保证第一次调用会先进入流中。

参考文献:来自accepted proposal

  

后缀表达式从左到右进行计算。这包括   函数调用和成员选择表达式。

     

分配表达式从右到左进行评估。这个   包括复合作业。

     

从左到右评估移位运算符的操作数。在   总结一下,下面的表达式按顺序a进行评估   b,然后是c,然后是d:

     
      
  1. A·B
  2.   
  3. A-&GT,B
  4.   
  5. A-&GT; * B
  6.   
  7. a(b1,b2,b3)
  8.   
  9. b @ = a
  10.   
  11. 一个并[b]
  12.   
  13. a&lt;&lt; B'/ LI>   
  14. a&gt;&gt; B'/ LI>         

    此外,我们建议采用以下附加规则:顺序   评估涉及重载运算符的表达式   由与相应内置相关联的顺序确定   运算符,而不是函数调用的规则。

修改记事:我原来的回答误解了a(b1, b2, b3)b1b2b3的顺序仍然未指定。 (谢谢@KABoissonneault,所有评论者。)

但是,(正如@Yakk指出的那样)这很重要:即使b1b2b3是非平凡的表达式,每个表达式都会被完全评估并且在开始评估其他函数参数之前绑定到相应的函数参数。标准声明如下:

  

§5.2.2 - 函数调用5.2.2.4:

     

。 。 。   后缀表达式在每个表达式之前排序   表达式列表和任何默认参数。每个值计算和   与参数初始化相关的副作用,以及   初始化本身,在每个值计算之前进行排序   与任何后续的初始化相关的副作用   参数。

然而,github draft中缺少其中一个新句子:

  

与...相关的每个值计算和副作用   初始化参数,初始化本身就是   在每个值计算和副作用相关之前排序   随着任何后续参数的初始化。

示例在那里。它解决了几十年前的问题(As explained by Herb Sutter),例如

之类的例外情况
f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(),get_raw_a()); 
如果其中一个调用get_raw_a()将在另一个之前抛出,

将泄漏 原始指针绑定了它的智能指针参数。 编辑:正如T.C.所指出的那样。该示例存在缺陷,因为原始指针的unique_ptr构造是显式的,因此无法编译。

另请注意这个经典的question(标记为 C ,而非 C ++ ):

int x=0;
x++ + ++x;

仍未定义。

答案 1 :(得分:34)

C ++ 17中禁止交错

在C ++ 14中,以下内容不安全:

void foo(std::unique_ptr<A>, std::unique_ptr<B> );

foo(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));

在函数调用

期间,此处有四个操作
  1. new A
  2. unique_ptr<A>构造函数
  3. new B
  4. unique_ptr<B>构造函数
  5. 这些的排序完全没有说明,因此完全有效的排序是(1),(3),(2),(4)。如果选择了这个顺序并且(3)抛出,则(1)的内存泄漏 - 我们还没有运行(2),这将防止泄漏。

    在C ++ 17中,新规则禁止交错。来自[intro.execution]:

      

    对于每个函数调用F,对于在F内发生的每个评估A以及在F中不发生但在同一线程上作为同一信号处理程序(如果有)的一部分进行评估的每个评估B,A是在A或B之前对B或B进行测序。

    该句的脚注如下:

      

    换句话说,函数执行不会相互交错。

    这给我们留下了两个有效的排序:(1),(2),(3),(4)或(3),(4),(1),(2)。没有说明采取哪种排序,但这两种都是安全的。现在禁止在(2)和(4)之前发生(1)(3)的所有排序。

答案 2 :(得分:1)

我发现了一些有关表达式评估顺序的注意事项:

  • Quick Q: Why doesn’t c++ have a specified order for evaluating function arguments?
      

    某些评估顺序可以保证在C ++ 17中添加重载运算符和完整参数规则。但是,仍然没有确定先争论哪个。 在C ++ 17中,现在已指定,该表达式给出了要调用的内容((函数调用的()左侧的代码)在参数之前,而首先计算的参数是

    在下一个方法开始之前,对它进行完全评估,对于对象方法,则在该方法的参数之前对对象的值进行评估。

  • Order of evaluation
      

    21)以括号分隔的初始值设定项中用逗号分隔的表达式列表中的每个表达式都像对函数调用那样进行评估(不确定顺序

  • Ambiguous expressions
      

    C ++语言不能保证函数调用的参数的求值顺序。

P0145R3.Refining Expression Evaluation Order for Idiomatic C++中,我发现:

  

后缀表达式的值计算和相关副作用在表达式列表中的表达式之前进行排序。声明的参数的初始化是不确定顺序,没有交织。

但是我没有在标准中找到它,而是在标准中找到了

  

6.8.1.8 Sequential execution [intro.execution]   如果每个值计算和与表达式X相关的每个副作用都在每个值计算和与表达式Y相关的每个副作用之前被排序,则称表达式X在表达式Y之前排序。

     

6.8.1.9 Sequential execution [intro.execution]   在与要评估的下一个完整表达式相关联的每个值计算和副作用之前,对与一个完整表达式相关联的每个值计算和副作用进行排序。

     

7.6.19.1 Comma operator [expr.comma]   一对用逗号分隔的表达式从左到右求值; ...

因此,我比较了三种针对14和17标准的编译器的行为。浏览的代码是:

#include <iostream>

struct A
{
    A& addInt(int i)
    {
        std::cout << "add int: " << i << "\n";
        return *this;
    }

    A& addFloat(float i)
    {
        std::cout << "add float: " << i << "\n";
        return *this;
    }
};

int computeInt()
{
    std::cout << "compute int\n";
    return 0;
}

float computeFloat()
{
    std::cout << "compute float\n";
    return 1.0f;
}

void compute(float, int)
{
    std::cout << "compute\n";
}

int main()
{
    A a;
    a.addFloat(computeFloat()).addInt(computeInt());
    std::cout << "Function call:\n";
    compute(computeFloat(), computeInt());
}

结果(更一致的是clang):

<style type="text/css">
  .tg {
    border-collapse: collapse;
    border-spacing: 0;
    border-color: #aaa;
  }
  
  .tg td {
    font-family: Arial, sans-serif;
    font-size: 14px;
    padding: 10px 5px;
    border-style: solid;
    border-width: 1px;
    overflow: hidden;
    word-break: normal;
    border-color: #aaa;
    color: #333;
    background-color: #fff;
  }
  
  .tg th {
    font-family: Arial, sans-serif;
    font-size: 14px;
    font-weight: normal;
    padding: 10px 5px;
    border-style: solid;
    border-width: 1px;
    overflow: hidden;
    word-break: normal;
    border-color: #aaa;
    color: #fff;
    background-color: #f38630;
  }
  
  .tg .tg-0pky {
    border-color: inherit;
    text-align: left;
    vertical-align: top
  }
  
  .tg .tg-fymr {
    font-weight: bold;
    border-color: inherit;
    text-align: left;
    vertical-align: top
  }
</style>
<table class="tg">
  <tr>
    <th class="tg-0pky"></th>
    <th class="tg-fymr">C++14</th>
    <th class="tg-fymr">C++17</th>
  </tr>
  <tr>
    <td class="tg-fymr"><br>gcc 9.0.1<br></td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
  </tr>
  <tr>
    <td class="tg-fymr">clang 9</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute float<br>compute int<br>compute</td>
  </tr>
  <tr>
    <td class="tg-fymr">msvs 2017</td>
    <td class="tg-0pky">compute int<br>compute float<br>add float: 1<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
    <td class="tg-0pky">compute float<br>add float: 1<br>compute int<br>add int: 0<br>Function call:<br>compute int<br>compute float<br>compute</td>
  </tr>
</table>