到目前为止,评估顺序未指定的一些常见情况已通过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,第 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 之前,不能保证第一个调用会首先进入流。
参考:来自the accepted proposal:
后缀表达式从左到右计算。这包括
函数调用和成员选择表达式。
赋值表达式从右到左计算。这
包括复合作业。
移位运算符的操作数从左到右计算。在
总结,以下表达式按 a 的顺序求值,然后
b,然后是 c,然后是 d:
- a.b
- a->b
- a->*b
- a(b1, b2, b3)
- b @= a
- a[b]
- 一个
- a >> b
此外,我们建议以下附加规则:
涉及重载运算符的表达式的评估是
由与相应内置相关联的顺序确定
运算符,而不是函数调用的规则。
编辑说明:我的原始答案误解了a(b1, b2, b3)。 b1、b2、b3 的顺序仍未指定。 (谢谢@KABoissonneault,所有评论者。)
但是,(正如@Yakk 指出的那样)这很重要:即使b1、b2、b3 是不平凡的表达式,它们中的每一个都被完全评估并与各自的函数参数 在开始评估其他参数之前。标准是这样规定的:
§5.2.2 - 函数调用 5.2.2.4:
。 . .
后缀表达式在每个表达式之前排序
表达式列表和任何默认参数。每个值计算和
与参数初始化相关的副作用,以及
初始化本身,在每个值计算之前排序,并且
与任何后续初始化相关的副作用
参数。
但是,the 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;
仍未定义。