10

这是我用来索引标记的常见模式:检查标记是否在地图中,如果没有,则将其添加到地图中,并分配地图的大小。

在 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”和零放入地图的函数。但它似乎在开始分配后对其进行评估,这没有任何意义。

在赋值操作之前不应该评估右手边吗?

4

4 回答 4

6

从迂腐的角度来说,这种行为是未指定的。

但是在你的情况下发生的是这样的:

m['A'] = m.size();

mstd::map如果键不存在,它会创建一个新条目。

在您的情况下,密钥'A'不存在,因此它创建条目,并返回对值(默认创建)的引用,然后m.size()在您的情况下分配给该值。

如上所述,行为是未指定的,因为操作数的评估顺序未指定,这意味着m.size()可能在之前评估m['A']。如果是,那么m['A']将是0,不是1

于 2013-08-04T05:41:19.527 回答
5

不。

(§5.17/1):“在所有情况下,赋值都在左右操作数的值计算之后和赋值表达式的值计算之前进行排序。”

但是请注意,虽然赋值发生在评估左右操作数之后,但在评估左右操作数本身之间没有指定顺序。因此,可以先评估左侧,然后评估右侧,反之亦然。

于 2013-08-04T05:44:45.510 回答
4

但它似乎在开始分配后对其进行评估......

正如this question的答案所示,赋值中的计算顺序实际上是未指定的(左手表达式还是右手表达式首先被计算是未指定的)。

如果m.size()首先评估,那么您的代码将按预期工作,但您不能保证这种行为,并且可能会m['A']首先评估另一个实现,与您的情况相同。必须避免这些模棱两可的情况。

最好做这样的事情

auto size = m.size();
m['A'] = size;

您可以保证在元素分配之前首先评估大小查询。

改进后的实时代码。.

于 2013-08-04T05:42:33.317 回答
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

参考std::map::operator[]

如果 key 与容器中任何元素的 key 不匹配,则该函数使用该 key 插入一个新元素并返回对其映射值的引用。请注意,这总是将容器大小增加一,即使没有为元素分配映射值(该元素是使用其默认构造函数构造的)。

编辑 :

正如其他人指出的那样,m['A'] = m.size();会导致未指定的行为,切勿使用此类语句,而是可以先计算大小,然后将其分配给新键。

于 2013-08-04T05:51:11.460 回答