您专门参考了 C++11 标准,所以我将用 C++11 的答案来回答。然而,它与 C++03 的答案非常相似,但排序的定义不同。
C++11 定义了单个线程上的求值之间的顺序关系。它是不对称的、传递的和成对的。如果某个评估 A 没有在某个评估 B 之前排序,并且 B 也没有在 A 之前排序,那么这两个评估是unsequenced。
评估表达式包括值计算(计算某个表达式的值)和副作用。副作用的一个实例是对象的修改,这是回答问题最重要的一个。其他事情也算作副作用。如果一个副作用相对于同一对象上的另一个副作用或值计算是未排序的,那么您的程序具有未定义的行为。
所以就是这样设置的。第一个重要的规则是:
与完整表达式关联的每个值计算和副作用在与要评估的下一个完整表达式关联的每个值计算和副作用之前排序。
因此,任何完整表达式都会在下一个完整表达式之前进行完整评估。在你的问题中,我们只处理一个完整的表达式,即i = v[i++]
,所以我们不需要担心这个。下一个重要规则是:
除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的求值是无序的。
这意味着在 中a + b
,例如,对a
和的评估b
是无序的(它们可以按任何顺序进行评估)。现在是我们最后的重要规则:
运算符的操作数的值计算在运算符结果的值计算之前排序。
所以对于a + b
,sequenced before 关系可以用一棵树来表示,其中有向箭头表示sequenced before 关系:
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
赋值运算符之前的值无关紧要,因为无论如何我们都会重写它。但实际上,在一般情况下,这是不正确的。我们可以覆盖赋值运算符并在赋值之前使用对象的值。标准并不关心我们不使用该值 - 规则被定义为使任何具有副作用的未排序的值计算都将是未定义的行为。没有但是。这种未定义的行为允许编译器发出更优化的代码。如果我们为赋值运算符添加排序,则无法使用此优化。