你特别提到了C++11标准,所以我会用C++11的答案回答。然而,它与C++03的答案非常相似,但序列化的定义不同。
C++11在单个线程的计算之间定义了一种先于排序关系。它是非对称的、可传递的和成对的。如果某个计算A不是先于某个计算B,且B也不是先于A,则这两个计算是未排序的。
计算一个表达式包括值的计算(求出某个表达式的值)和副作用。 副作用 的一种实例是对象的修改,这是回答问题最重要的一点。其他事情也算是副作用。如果一个副作用相对于同一对象上的另一个副作用或值计算未排序,则您的程序具有未定义的行为。
所以这就是设置。第一个重要规则是:
与完整表达式相关的每个值计算和副作用都先于将要计算的下一个完整表达式的每个值计算和副作用。
因此,在下一个完整表达式之前,任何完整表达式都会被完全评估。在你的问题中,我们只处理一个完整表达式,即i = v [i ++]
,因此我们不需要担心这个问题。下一个重要规则是:
除非另有说明,否则各个运算符的操作数和各个表达式的子表达式的评估未排序。
这意味着在例如a + b
中,对a
和b
的评估是未排序的(它们可以以任何顺序评估)。现在轮到我们最后一个重要规则了:
操作符的操作数的值计算先于操作符结果的值计算。
因此,对于a + b
,排序关系可以用树表示,其中有向箭头表示排序关系:
a + b (value computation)
^ ^
| |
a b (value computation)
如果两个评估发生在树的不同分支中,则它们是无序的,因此该树表明
a
和
b
的评估相对于彼此是无序的。
现在,让我们对
i = v [i ++]
的示例执行相同的操作。我们利用了
v [i ++]
被定义为等价于
*(v +(i ++))
这一事实。我们还使用了有关后缀递增顺序的一些额外知识:
“++”表达式的值计算在修改操作数对象之前排序。
因此,在这里我们进行如下操作(树的节点是值计算,除非指定为副作用):
i = v[i++]
^ ^
| |
i★ v[i++] = *(v + (i++))
^
|
v + (i++)
^ ^
| |
v ++ (side effect on i)★
^
|
i
在这里,您可以看到对i
的副作用i++
在赋值运算符前面i
的使用(我用★标记了每个评估)是分开的。所以我们肯定有未定义的行为!如果您想知道您的评估排序是否会引起问题,我强烈推荐绘制这些图表。
现在我们提出一个问题,即赋值运算符之前的i
的值并不重要,因为我们无论如何都会覆盖它。但事实上,在一般情况下,这并不是真的。我们可以覆盖赋值运算符并利用赋值之前对象的值。标准并不关心我们不使用该值-规则被定义为具有任何值计算与副作用不排序将导致未定义的行为。没有例外。此未定义行为旨在允许编译器生成更优化的代码。如果我们为赋值运算符添加排序,就无法使用此优化。