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

新手上路,请多包涵

C++17 评估顺序保证 (P0145) 中的投票对典型 C++ 代码有何影响?

它对以下内容有何改变?

 i = 1;
f(i++, i)

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

或者

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

原文由 Johan Lundberg 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 642
1 个回答

到目前为止,评估顺序尚未 指定 的一些常见情况已通过 C++17 指定并有效。一些未定义的行为现在是未指定的。

>  i = 1;
> f(i++, i)
>
> ```

未定义,但现在未指定。具体来说,没有指定的是 `f` 的每个参数相对于其他参数的评估顺序。 `i++` 可能在 `i` 之前进行评估,反之亦然。事实上,它可能会以不同的顺序评估第二次调用,尽管它在同一个编译器下。

但是,在执行任何其他参数之前,每个参数的评估都 _需要_ 完全执行,并具有所有副作用。所以你可能会得到 `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, "");
>
> ```

该示例来自 _The C++ Programming Language_ , 4th edition, Stroustrup,并且曾经是未指定的行为,但使用 C++17 它将按预期工作。可恢复功能( `.then( . . . )` )也存在类似问题。

作为另一个示例,请考虑以下内容:

#include #include #include #include

struct Speaker{ int i =0; Speaker(std::vectorstd::string words) :words(words) {} std::vectorstd::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 之前,不能保证第一个调用会首先进入流。

参考文献:来自 [已接受的提案](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0145r3.pdf):

> 后缀表达式从左到右计算。这包括函数调用和成员选择表达式。
>
> 赋值表达式从右到左求值。这包括复合作业。
>
> 移位运算符的操作数从左到右求值。总之,以下表达式的计算顺序是 a,然后是 b,然后是 c,然后是 d:
>
> 1. 抗体
> 2. a->b
> 3. a->\*b
> 4. a(b1, b2, b3)
> 5. b @= 一个
> 6. 一个\[b\]
> 7. 一个 << 乙
> 8. 一 >\> 乙
>
> 此外,我们建议以下附加规则:涉及重载运算符的表达式的求值顺序由与相应内置运算符关联的顺序决定,而不是函数调用的规则。

**编辑说明:** 我的原始答案误解 `a(b1, b2, b3)` 。 `b1` , `b2` , `b3` 的顺序仍未指定。 (谢谢@KABoissonneault,所有评论者。)

但是,(正如@Yakk 指出的那样)这很重要:即使 `b1` 、 `b2` 、 `b3` 都是不平凡的表达式在开始评估其他 _函数参数之前,已评估并绑定到相应的函数参数_。该标准是这样规定的:

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

但是, [GitHub 草案](https://github.com/cplusplus/draft/blob/master/source/expressions.tex#L1585) 中缺少这些新句子之一:

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

例子 _就_ 在那里。它解决了几十年前的问题( [如 Herb Sutter 所解释的](https://herbsutter.com/2016/06/30/trip-report-summer-iso-c-standards-meeting-oulu/)),具有异常安全性,例如

f(std::unique_ptr a, std::unique_ptr b);

f(get_raw_a(), get_raw_a());

”`

如果其中一个调用 get_raw_a() 会在另一个原始指针绑定到其智能指针参数之前抛出,则会泄漏。

正如 TC 所指出的,该示例存在缺陷,因为从原始指针构造 unique_ptr 是显式的,因此无法编译。 *

还要注意这个经典 问题(标记为 C ,而不是 C++ ):

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

仍然未定义。

原文由 Johan Lundberg 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题