map<int, int> mp;
printf("%d ", mp.size());
mp[10]=mp.size();
printf("%d\n", mp[10]);
此代码产生的答案不是很直观:
0 1
我理解为什么会发生这种情况 - 赋值的左侧返回对mp[10]
的基础值的引用,同时创建上述值,然后使用新计算的{评估右侧{1}}地图。
这种行为在C ++标准中的任何地方都有说明吗?或者评估顺序是否未定义?
使用g ++ 5.2.1获得结果。
答案 0 :(得分:17)
是的,这是标准所涵盖的,它是未指明的行为。最近的C ++标准提案涵盖了这一特殊情况:N4228: Refining Expression Evaluation Order for Idiomatic C++旨在优化评估规则的顺序,使其在某些情况下得到明确规定。
它描述了这个问题如下:
表达式评估顺序是C ++中反复出现的讨论主题 社区。简而言之,给出一个表达式,例如 f(a,b, c),评估子表达式 f,a,b,c 的顺序未被标准指定。如果这些子表达式中的任何两个碰巧修改同一个对象而没有插入序列点,则程序的行为是不确定的。例如,表达式 f(i ++,i),其中i是 整数变量导致未定义的行为, v [i]也是如此 = i ++ 。即使行为未定义,评估表达式的结果仍然可以是任何人的猜测。考虑 以下程序片段:
#include <map> int main() { std::map<int, int> m; m[0] = m.size(); // #1 }
评估后,地图对象应该是什么样子? 声明标记为#1? {{0,0}}或{{0,1}}?
我们知道除非指定子表达式的评估未被排序,否则这来自draft C++11 standard部分1.9
程序执行,其中包含:
除非另有说明,否则评估各个运营商的操作数 个别表达的子表达式没有序列。[...]
所有部分5.17
分配和复合赋值运算符[expr.ass]表示:
[...]在所有情况下,赋值都在值之后排序 计算右和左操作数,并在赋值表达式的值计算之前。[...]
因此,本节并未确定评估顺序,但我们知道这不是未定义的行为,因为operator []
和size()
都是函数调用,而1.9
部分告诉我们(强调我的):
[...]调用函数时(无论函数是否为内联函数),每个值计算和副作用 与任何参数表达式相关联,或与指定被调用函数的后缀表达式相关联 在执行被调用函数体内的每个表达式或语句之前进行排序。 [注意:价值 与不同参数表达式相关的计算和副作用是无法排序的。 - 尾注] 调用函数中的每个评估(包括其他函数调用)都没有特别说明 在执行被调用函数体之前或之后对序列进行测序是不确定的 尊重被调用函数的执行 .9 [...]
请注意,我在问题Does this code from “The C++ Programming Language” 4th edition section 36.3.6 have well-defined behavior?中涵盖N4228
提案中的第二个有趣示例。
似乎N4228
的修订版本为accepted by the Evolution Working Group at the last WG21 meeting,但论文(P0145R0)尚未提供。所以这在C ++ 17中可能不再是未指定的。
p0145的修订版3指定并更新[expr.ass]p1:
赋值运算符(=)和复合赋值运算符从右到左分组。 所有都需要一个可修改的左值作为左操作数;它们的结果是左值操作数的左值。 如果左操作数是位字段,则所有情况下的结果都是位字段。 在所有情况下,在右和左操作数的值计算之后,以及在赋值表达式的值计算之前,对赋值进行排序。 右操作数在左操作数之前排序。 ...
答案 1 :(得分:8)
从C ++ 11标准(强调我的):
5.17分配和复合赋值运算符
1赋值运算符(=)和复合赋值运算符从右到左分组。所有都需要一个可修改的左值作为左操作数,并返回一个左值操作数的左值。如果左操作数是位字段,则所有情况下的结果都是位字段。在所有情况下,在右和左操作数的值计算之后,以及在赋值表达式的值计算之前对赋值进行排序。
是否首先评估左操作数或首先评估右操作数不是由语言指定的。编译器可以自由选择首先评估任一操作数。由于代码的最终结果取决于操作数的评估顺序,我会说它是未指定的行为而不是未定义的行为。
1.3.25未指明的行为
行为,对于格式正确的程序构造和正确的数据,取决于实现
答案 2 :(得分:3)
我确定标准没有为表达式x = y;
指定在C ++标准中评估x
或y
的顺序(这就是为什么你可以&例如,*p++ = *p++
,因为p++
未按照定义的顺序完成。)
换句话说,为了按照定义的顺序保证订单x = y;
,您需要将其分解为两个序列点。
T tmp = y;
x = tmp;
(当然,在这种特殊情况下,可能会假设编译器更喜欢在operator[]
之前执行size()
,因为它可以将值直接存储到operator[]
的结果中而不是将它保存在临时位置,以便在评估operator[]
之后存储它 - 但我很确定编译器不需要按顺序执行它)
答案 3 :(得分:1)
让我们来看看您的代码分解为:
mp.operator[](10).operator=(mp.size());
这几乎说明了在第一部分中创建了10的条目,在第二部分中,容器的大小被分配给位置为10的整数引用。
但是现在你进入了未指明的评估问题的顺序。这是一个更简单的example。
map::size()
之前或之后应该map::operator(int const &);
何时被调用?
没有人真的知道。