使用+=运算符可以安全地使用[]创建新的std::map条目

Safe to use += operator to create a new std::map entry using []?

本文关键字:创建 std 条目 map 运算符 安全 使用      更新时间:2023-10-16

假设我有一个std::map<int, int>,这样做安全吗?

std::map<int, int> m_map;
m_map[0] += 1;

如果在我执行此操作时映射中不存在关键字0,它怎么知道要将1添加到哪个值?

我希望std::map通过执行=而不是+=来处理这一问题,因为该值在映射中创建了一个新条目。这将使我不必做:

std::map<int, int>::iterator it = m_map.find(0);
if(it != m_map.end()) {
    it->second += 1;
}
else {
    m_map[0] = 1;
}

在调用operator[]时,由于先前未映射的键而插入到映射中的元素是初始化的值。如果您没有见过该语法,请考虑()在以下代码片段中的特殊含义。parens很重要。它们在初始化树中引入了与默认初始化不同的跳闸。两者都很重要;两者都是按照语言标准制定的。

int i = int();

事实证明,标量(包括指针)的值初始化最终屈服于零初始化。虽然看起来很奇怪,但前面的代码段值初始化int的一个实例,由于int是标量,该实例变为零初始化,然后将其复制到i。(公平地说,几乎肯定会有一些淘汰,但基本面是这样的)。

无论如何,由于该功能,当您这样做时,您可以放心:

m_map[0] += 1;

甚至这个:

++m_map[0];

如果索引之前没有映射,则会添加一个值初始化的元素,这将为标量进行零初始化,这意味着您将正式从zero开始。

值得一提的是,对于具有隐式声明构造函数的任何类型,都会发生类似的活动。不管琐碎与否,都会发生一些有趣的事情。

struct S { int a; int b; };
std::map<int, S> mymap;
++mymap[0].a;

执行以上操作后,我们容器中a成员映射到01是否可靠是的是。此外,考虑一下:

struct S { int a; std::string str; };
std::map<int, S> mymap;
++mymap[0].a;

现在S有一个非平凡的隐式构造函数(它必须这样做,因为它必须构造str)。但是,在我们的容器中映射到0a成员是否仍然可靠地初始化为零(因此,在上行之后的1是的是。

如果对引用的不同初始化路径感到好奇,请参阅此问答。或者查看C++11标准,特别是C++11§8.5初始化程序(p5、p7、p10)。值得一读。

即使使用较短的代码段,也可以保证获得1。

关键是operator[]必须在返回对它的引用之前在映射中创建元素,所以当你到达+=时,元素已经存在,如果现在必须创建,则值为零(因为映射的元素是值初始化的)。

(顺便说一句,这就是为什么当您使用以类类型为值的std::map时,如果您想使用operator[],即使您立即为其分配对象,它也必须具有默认构造函数的原因)

是的,这很好,新条目默认为值value_type(),即int0

它仍然做+=,但0 += 1给出1

Map使用默认构造函数进行延迟初始化。(所有int为零)

因此,如果映射中不存在0,则会将其初始化为包含值0,并在+= 1之后添加1。

因此,您可以毫无顾虑地使用更高版本的代码。

引用从cplusplus.com 调用map_obj[k]的效果

如果k与容器中某个元素的键匹配,则函数返回对其映射值的引用。

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