插入地图的首选/惯用方式是什么?
我已经确定了将元素插入 std::map 的四种不同方法:
std::map<int, int> function;
function[0] = 42;
function.insert(std::map<int, int>::value_type(0, 42));
function.insert(std::pair<int, int>(0, 42));
function.insert(std::make_pair(0, 42));
哪一种是首选/惯用方法? (还有其他我没有想到的方法吗?)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
从 C++11 开始,您有两个主要的附加选项。首先,您可以将
insert()
与列表初始化语法一起使用:这在功能上等效,
但更加简洁和可读。正如其他答案所指出的,这比其他形式有几个优点:
operator[]
方法可以覆盖现有元素,并且使您无法判断是否发生了这种情况。insert
涉及隐式类型转换,这可能会减慢您的代码速度。主要缺点是这种形式过去要求键和值是可复制的,因此它不适用于具有
unique_ptr
值的映射。该问题已在标准中修复,但修复可能尚未达到您的标准库实现。其次,您可以使用
emplace()
方法:这比
insert()
的任何形式都更简洁,适用于仅移动类型,例如unique_ptr
,理论上可能会稍微更高效(尽管一个像样的编译器应该优化掉差异)。唯一的主要缺点是,它可能会让您的读者感到有点惊讶,因为emplace
方法通常不以这种方式使用。As of C++11, you have two major additional options. First, you can use
insert()
with list initialization syntax:This is functionally equivalent to
but much more concise and readable. As other answers have noted, this has several advantages over the other forms:
operator[]
approach requires the mapped type to be assignable, which isn't always the case.operator[]
approach can overwrite existing elements, and gives you no way to tell whether this has happened.insert
that you list involve an implicit type conversion, which may slow your code down.The major drawback is that this form used to require the key and value to be copyable, so it wouldn't work with e.g. a map with
unique_ptr
values. That has been fixed in the standard, but the fix may not have reached your standard library implementation yet.Second, you can use the
emplace()
method:This is more concise than any of the forms of
insert()
, works fine with move-only types likeunique_ptr
, and theoretically may be slightly more efficient (although a decent compiler should optimize away the difference). The only major drawback is that it may surprise your readers a little, sinceemplace
methods aren't usually used that way.首先,
operator[]
和insert
成员函数在功能上并不等效:operator[]
将搜索对于键,如果未找到,则插入一个默认构造值,并返回您分配值的引用。显然,如果mapped_type可以受益于直接初始化而不是默认构造和分配,那么这可能是低效的。此方法还使得无法确定是否确实发生了插入,或者您是否仅覆盖了先前插入的键的值。std::pair
(最值得注意的是确定插入是否实际上已完成)。从所有列出的调用
insert
的可能性来看,所有三个都几乎等效。作为提醒,让我们看一下标准中的insert
签名:那么这三个调用有何不同?
std::make_pair
依赖于模板参数推导,并且可能(在本例中将会)生成与实际value_type
不同类型的内容地图,这将需要额外调用std::pair
模板构造函数才能转换为value_type
(即:将const
添加到 < code>first_type)std::pair
还需要额外调用std::pair
的模板构造函数才能转换value_type
的参数(即:将const
添加到first_type
)std::map::value_type 绝对没有任何疑问,因为它直接是
insert
成员函数所期望的参数类型。最后,当目标是插入时,我会避免使用
operator[]
,除非默认构造和分配mapped_type
没有额外的成本,并且我不关心确定新密钥是否已有效插入。使用insert
时,构造value_type
可能是正确的方法。First of all,
operator[]
andinsert
member functions are not functionally equivalent :operator[]
will search for the key, insert a default constructed value if not found, and return a reference to which you assign a value. Obviously, this can be inefficient if themapped_type
can benefit from being directly initialized instead of default constructed and assigned. This method also makes it impossible to determine if an insertion has indeed taken place or if you have only overwritten the value for an previously inserted keyinsert
member function will have no effect if the key is already present in the map and, although it is often forgotten, returns anstd::pair<iterator, bool>
which can be of interest (most notably to determine if insertion has actually been done).From all the listed possibilities to call
insert
, all three are almost equivalent. As a reminder, let's have look atinsert
signature in the standard :So how are the three calls different ?
std::make_pair
relies on template argument deduction and could (and in this case will) produce something of a different type than the actualvalue_type
of the map, which will require an additional call tostd::pair
template constructor in order to convert tovalue_type
(ie : addingconst
tofirst_type
)std::pair<int, int>
will also require an additional call to the template constructor ofstd::pair
in order to convert the parameter tovalue_type
(ie : addingconst
tofirst_type
)std::map<int, int>::value_type
leaves absolutely no place for doubt as it is directly the parameter type expected by theinsert
member function.In the end, I would avoid using
operator[]
when the objective is to insert, unless there is no additional cost in default-constructing and assigning themapped_type
, and that I don't care about determining if a new key has effectively inserted. When usinginsert
, constructing avalue_type
is probably the way to go.自 C++17
std::map
提供两种新的插入方法:insert_or_assign()
和try_emplace()
,正如 sp2danny 的评论。insert_or_assign()
基本上,
insert_or_assign()
是操作符[]
。与operator[]
相比,insert_or_assign()
不要求映射的值类型默认可构造。例如,以下代码无法编译,因为MyClass
没有默认构造函数:但是,如果将
myMap[0] = MyClass(1);
替换为以下行,然后代码将编译并按预期进行插入:此外,类似于
insert()
、insert_or_assign()
返回一个pair
。如果发生插入,则布尔值为true
;如果完成赋值,则布尔值为false
。迭代器指向被插入或更新的元素。try_emplace()
与上面类似,
try_emplace()
是emplace()
。与emplace()
相比,如果由于映射中已存在键而导致插入失败,try_emplace()
不会修改其参数。例如,以下代码尝试使用已存储在映射中的键来放置元素(请参阅 *):输出(至少对于 VS2017 和 Coliru):
如您所见,
pMyObj
不再指向原始对象。但是,如果将auto [it, b] = myMap2.emplace(0, std::move(pMyObj));
替换为以下代码,则输出看起来会有所不同,因为pMyObj
保持不变:输出:
Coliru 上的代码
请注意:我试图将我的解释保留为尽可能简短,使它们适合这个答案。要获得更准确和全面的描述,我建议阅读 这篇文章介绍Fluent C++。
Since C++17
std::map
offers two new insertion methods:insert_or_assign()
andtry_emplace()
, as also mentioned in the comment by sp2danny.insert_or_assign()
Basically,
insert_or_assign()
is an "improved" version ofoperator[]
. In contrast tooperator[]
,insert_or_assign()
doesn't require the map's value type to be default constructible. For example, the following code doesn't compile, becauseMyClass
does not have a default constructor:However, if you replace
myMap[0] = MyClass(1);
by the following line, then the code compiles and the insertion takes place as intended:Moreover, similar to
insert()
,insert_or_assign()
returns apair<iterator, bool>
. The Boolean value istrue
if an insertion occurred andfalse
if an assignment was done. The iterator points to the element that was inserted or updated.try_emplace()
Similar to the above,
try_emplace()
is an "improvement" ofemplace()
. In contrast toemplace()
,try_emplace()
doesn't modify its arguments if insertion fails due to a key already existing in the map. For example, the following code attempts to emplace an element with a key that is already stored in the map (see *):Output (at least for VS2017 and Coliru):
As you can see,
pMyObj
no longer points to the original object. However, if you replaceauto [it, b] = myMap2.emplace(0, std::move(pMyObj));
by the the following code, then the output looks different, becausepMyObj
remains unchanged:Output:
Code on Coliru
Please note: I tried to keep my explanations as short and simple as possible to fit them into this answer. For a more precise and comprehensive description, I recommend reading this article on Fluent C++.
第一个版本:
可能会也可能不会将值 42 插入到映射中。如果键
0
存在,那么它将为该键分配 42,覆盖该键所具有的任何值。否则它会插入键/值对。插入功能:
另一方面,如果地图中已存在键
0
,则不执行任何操作。如果键不存在,则会插入键/值对。三个插入函数几乎相同。
std::map::value_type
是std::pair
的typedef
,并且 < code>std::make_pair() 显然通过模板推导魔法产生了一个std::pair<>
。然而,版本 2、3 和 4 的最终结果应该是相同的。我会使用哪一个?我个人更喜欢版本1;它简洁而“自然”。当然,如果不需要它的覆盖行为,那么我更喜欢版本 4,因为它比版本 2 和 3 需要更少的输入。我不知道是否有一种事实上的方式将键/值对插入到 std::map 中。
通过其构造函数之一将值插入映射的另一种方法:
The first version:
may or may not insert the value 42 into the map. If the key
0
exists, then it will assign 42 to that key, overwriting whatever value that key had. Otherwise it inserts the key/value pair.The insert functions:
on the other hand, don't do anything if the key
0
already exists in the map. If the key doesn't exist, it inserts the key/value pair.The three insert functions are almost identical.
std::map<int, int>::value_type
is thetypedef
forstd::pair<const int, int>
, andstd::make_pair()
obviously produces astd::pair<>
via template deduction magic. The end result, however, should be the same for versions 2, 3, and 4.Which one would I use? I personally prefer version 1; it's concise and "natural". Of course, if its overwriting behavior is not desired, then I would prefer version 4, since it requires less typing than versions 2 and 3. I don't know if there is a single de facto way of inserting key/value pairs into a
std::map
.Another way to insert values into a map via one of its constructors:
如果要覆盖键为0的元素
否则:
If you want to overwrite the element with key 0
Otherwise:
我一直在上述版本之间进行一些时间比较:
事实证明,插入版本之间的时间差异很小。
这分别给出了版本(我运行文件 3 次,因此每个都有 3 个连续时间差):
2198 ms、2078 ms、2072 ms
2290 ms、2037 ms、2046 ms
2592 ms、2278 ms、2296 ms
2234 ms ,2031 ms,2027 ms
因此,不同插入版本之间的结果可以忽略(尽管没有执行假设检验)!
由于使用 Widget 的默认构造函数进行初始化,
map_W_3[it] = Widget(2.0);
版本在此示例中花费了大约 10-15% 的时间。I have been running some time comparisons between the abovementioned versions:
Turns out that time differences between the insert versions are tiny.
This gives respectively for the versions (I ran the file 3 times, hence the 3 consecutive time differences for each):
2198 ms, 2078 ms, 2072 ms
2290 ms, 2037 ms, 2046 ms
2592 ms, 2278 ms, 2296 ms
2234 ms, 2031 ms, 2027 ms
Hence, results between different insert versions can be neglected (didn't perform a hypothesis test though)!
The
map_W_3[it] = Widget(2.0);
version takes about 10-15 % more time for this example due to an initialization with the default constructor for Widget.简而言之,
[]
运算符更新值的效率更高,因为它涉及调用值类型的默认构造函数,然后为其分配新值,而insert()
则更高效有效地增加价值。引用 Scott Meyers 的《有效的 STL:提高标准模板库使用的 50 种具体方法》第 24 条中的片段可能会有所帮助。
您可能决定选择一个通用的免编程版本,但重点是我发现这个范例(区分“添加”和“更新”)非常有用。
In short,
[]
operator is more efficient for updating values because it involves calling default constructor of the value type and then assigning it a new value, whileinsert()
is more efficient for adding values.The quoted snippet from Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library by Scott Meyers, Item 24 might help.
You may decide to choose a generic-programming-free version of this, but the point is that I find this paradigm (differentiating 'add' and 'update') extremely useful.
如果你想在 std::map 中插入元素 - 使用 insert() 函数,如果你想找到元素(通过键)并为其分配一些元素 - 使用运算符[]。
为了简化插入,请使用 boost::assign 库,如下所示:
If you want to insert element in std::map - use insert() function, and if you want to find element (by key) and assign some to it - use operator[].
For simplify inserting use boost::assign library, like this:
我只是稍微改变一下问题(字符串映射)以显示插入的另一个兴趣:
编译器在“ranking[1] = 42;”上没有显示错误的事实可能会产生毁灭性的影响!
I just change the problem a little bit (map of strings) to show another interest of insert:
the fact that compiler shows no error on "rancking[1] = 42;" can have devastating impact !