这是我用来索引令牌的常用模式:检查令牌是否在地图中,如果没有,则将其添加到地图中,分配地图的大小。
在C ++中执行此操作时,在进行分配之前意外地增加了地图的大小:
#include <cstdio>
#include <map>
using namespace std;
int main() {
map<char, int> m;
printf("Size before adding: %d\n", m.size());
m['A'] = m.size();
printf("Size after adding: %d\n", m.size());
printf("What was added: %d\n", m['A']);
return 0;
}
打印出来:
Size before adding: 0
Size after adding: 1
What was added: 1
我理解它的方式,它应评估右侧,即零,然后将其传递给将“A”和零置于地图中的函数。但它似乎是在开始分配之后对其进行评估,这没有任何意义。
在分配操作之前,不应评估右侧吗?
答案 0 :(得分:6)
这种行为是未指明的,迂腐地说。
但你的情况会发生什么:
m['A'] = m.size();
m
是std::map
,如果密钥不存在,则会创建新条目。
在您的情况下,键'A'
不存在,因此它创建条目,并返回对值的引用(默认创建),然后将其分配给您的m.size()
情况下。
如上所述,行为未指定,因为操作数的评估顺序未指定,这意味着m.size()
可能会在m['A']
之前进行评估。如果是,则m['A']
将为0
,而不是1
。
答案 1 :(得分:4)
但它似乎是在开始分配之后对其进行评估......
分配中的评估顺序实际上未指定(是否首先评估左手或右手表达是未指定的),如this question的答案所示。
如果首先评估m.size()
,那么您的代码将按预期工作,但您无法保证此行为,而另一个实现可能首先评估m['A']
,与您的情况相同。必须避免这些模糊的情况。
更好地做这样的事情
auto size = m.size();
m['A'] = size;
保证在元素赋值之前首先计算大小查询。
答案 2 :(得分:4)
没有
(§5.17/ 1):“在所有情况下,赋值在右和左操作数的值计算之后,以及赋值表达式的值计算之前排序。”
但请注意,虽然在评估右和左操作数之后进行赋值,但在左右操作数本身的评估之间未指定排序。因此,左边可以先评估,然后右边评估,反之亦然。
答案 3 :(得分:1)
这是[]
运算符的近似实现,从stl头文件编辑
mapped_type& operator[](const key_type& key){
auto itr = lower_bound(key);
// itr->first is greater than or equivalent to key.
if (itr == end() || comp_func(key, (*itr).first))
itr = insert(itr, value_type(key, mapped_type()));
return (*itr).second;
}
因此,您可以看到它首先插入新元素,从而将地图大小增加1
如果key与容器中任何元素的键不匹配,则为 function使用该键插入一个新元素并返回一个引用 到它的映射值。请注意,这总是会增加容器 即使没有为元素分配映射值,也将大小加1 element是使用其默认构造函数构造的。
修改:
正如其他人所指出的那样,m['A'] = m.size();
导致一个未指定的行为,从不使用这样的语句,而是先计算大小然后再将其分配给新密钥。