C++17 引入的求值顺序保证是什么? [英] What are the evaluation order guarantees introduced by C++17?

查看:45
本文介绍了C++17 引入的求值顺序保证是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

以下内容有何变化?

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

std::cout <<f()<

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

解决方案

一些常见的评估顺序目前未指定的情况,在 C++17C++17.一些未定义的行为现在改为未指定.

<块引用>

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

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

然而,在执行任何其他参数之前,要求对每个参数的评估完全执行,并具有所有副作用.因此,您可能会得到 f(1, 1)(首先计算第二个参数)或 f(1, 2)(首先计算第一个参数).但是你永远不会得到 f(2, 2) 或任何其他性质的东西.

<块引用>

std::cout <<f()<

未指定,但它将与运算符优先级兼容,因此 f 的第一个计算将首先出现在流中(下面的示例).

<块引用>

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

仍然有未指定的 g、h 和 j 的计算顺序.请注意,对于 getf()(g(),h(),j()),规则规定 getf() 将在 g 之前计算,h, j.

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

<块引用>

 std::string s = "但我听说即使你不相信它也能工作";s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(不要"), 6, ");

该示例来自 The C++ Programming Language,第 4 版,Stroustrup,曾经是未指定的行为,但在 C++17 中它会按预期工作.可恢复函数也存在类似问题 (.then( . . )).

再举一个例子,考虑以下内容:

#include #include <字符串>#include <向量>#include <cassert>结构扬声器{int i = 0;扬声器(std::vector<std::string> words) :words(words) {}std::vector字;std::string 运算符()(){断言(words.size()> 0);if(i==words.size()) i=0;//C++17 之前的版本:auto word = words[i] + (i+1==words.size()?"
":",");++i;回话;//使用 C++17 仍然不可能://返回 words[i++] + (i==words.size()?"
":",");}};int main() {auto spk = Speaker{{All"、Work"、and"、no"、play"}};std::cout <<spk()<

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

播放不,和,工作,所有,

代替

All,work,and,no,play

注意上面的效果是一样的

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

但是,在 C++17 之前,不能保证第一个调用会首先进入流.

参考文献:来自接受的提案:

<块引用>

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

赋值表达式从右到左计算.这个包括复合赋值.

移位运算符的操作数从左到右求值.在总结,以下表达式按顺序 a 求值,然后b,然后 c,然后 d:

  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a <<
  8. a >>

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

编辑说明:我的原始答案误解了a(b1, b2, b3).b1b2b3 的顺序仍未确定.(感谢@KABoissonneault,所有评论者.)

然而,(正如@Yakk 指出的)这很重要:即使 b1b2b3 是非平凡的表达式,它们中的每一个都被完全评估并绑定到各自的函数参数,然后才开始评估其他的.标准是这样规定的:

<块引用>

§5.2.2 - 函数调用 5.2.2.4:

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

但是,GitHub 中缺少这些新句子之一草稿:

<块引用>

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

示例.它解决了几十年前的问题(正如 Herb Sutter 所解释的那样),在诸如

之类的地方具有异常安全性

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

如果其中一个调用 get_raw_a() 会在另一个调用之前抛出,则会泄漏原始指针与其智能指针参数相关联.

正如 T.C. 所指出的,该示例存在缺陷,因为从原始指针构建的 unique_ptr 是显式的,从而阻止了编译.*

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

<块引用>

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

仍未定义.

What are the implications of the voted in C++17 evaluation order guarantees (P0145) on typical C++ code?

What does it change about things like the following?

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

and

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

or

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

解决方案

Some common cases where the evaluation order has so far been unspecified, are specified and valid with C++17. Some undefined behaviour is now instead unspecified.

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

was undefined, but it is now unspecified. Specifically, what is not specified is the order in which each argument to f is evaluated relative to the others. i++ might be evaluated before i, or vice-versa. Indeed, it might evaluate a second call in a different order, despite being under the same compiler.

However, the evaluation of each argument is required to execute completely, with all side-effects, before the execution of any other argument. So you might get f(1, 1) (second argument evaluated first) or f(1, 2) (first argument evaluated first). But you will never get f(2, 2) or anything else of that nature.

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

was unspecified, but it will become compatible with operator precedence so that the first evaluation of f will come first in the stream (examples below).

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

still has unspecified evaluation order of g, h, and j. Note that for getf()(g(),h(),j()), the rules state that getf() will be evaluated before g, h, j.

Also note the following example from the proposal text:

 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 example comes from The C++ Programming Language, 4th edition, Stroustrup, and used to be unspecified behaviour, but with C++17 it will work as expected. There were similar issues with resumable functions (.then( . . . )).

As another example, consider the following:

#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()?"
":",");
        ++i;
        return word;
        // Still not possible with C++17:
        // return words[i++] + (i==words.size()?"
":",");

    }
};

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

With C++14 and before we may (and will) get results such as

play
no,and,Work,All,

instead of

All,work,and,no,play

Note that the above is in effect the same as

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

But still, before C++17 there was no guarantee that the first calls would come first into the stream.

References: From the accepted proposal:

Postfix expressions are evaluated from left to right. This includes functions calls and member selection expressions.

Assignment expressions are evaluated from right to left. This includes compound assignments.

Operands to shift operators are evaluated from left to right. In summary, the following expressions are evaluated in the order a, then b, then c, then d:

  1. a.b
  2. a->b
  3. a->*b
  4. a(b1, b2, b3)
  5. b @= a
  6. a[b]
  7. a << b
  8. a >> b

Furthermore, we suggest the following additional rule: the order of evaluation of an expression involving an overloaded operator is determined by the order associated with the corresponding built-in operator, not the rules for function calls.

Edit note: My original answer misinterpreted a(b1, b2, b3). The order of b1, b2, b3 is still unspecified. (thank you @KABoissonneault, all commenters.)

However, (as @Yakk points out) and this is important: Even when b1, b2, b3 are non-trivial expressions, each of them are completely evaluated and tied to the respective function parameter before the other ones are started to be evaluated. The standard states this like this:

§5.2.2 - Function call 5.2.2.4:

. . . The postfix-expression is sequenced before each expression in the expression-list and any default argument. Every value computation and side effect associated with the initialization of a parameter, and the initialization itself, is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter.

However, one of these new sentences are missing from the GitHub draft:

Every value computation and side effect associated with the initialization of a parameter, and the initialization itself, is sequenced before every value computation and side effect associated with the initialization of any subsequent parameter.

The example is there. It solves a decades-old problems (as explained by Herb Sutter) with exception safety where things like

f(std::unique_ptr<A> a, std::unique_ptr<B> b);

f(get_raw_a(), get_raw_a());

would leak if one of the calls get_raw_a() would throw before the other raw pointer was tied to its smart pointer parameter.

As pointed out by T.C., the example is flawed since unique_ptr construction from raw pointer is explicit, preventing this from compiling.*

Also note this classical question (tagged C, not C++):

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

is still undefined.

这篇关于C++17 引入的求值顺序保证是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆