C++ 插入地图的首选/惯用方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4286670/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What is the preferred/idiomatic way to insert into a map?
提问by fredoverflow
I have identified four different ways of inserting elements into a std::map
:
我已经确定了四种不同的将元素插入到 a 中的方法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));
Which of those is the preferred/idiomatic way? (And is there another way I have not thought of?)
其中哪一种是首选/惯用的方式?(还有我没有想到的另一种方法吗?)
采纳答案by icecrime
First of all, operator[]
and insert
member functions are not functionally equivalent :
首先,operator[]
和insert
成员函数并不功能等效:
- The
operator[]
will searchfor the key, insert a default constructedvalue 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 key - The
insert
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).
- 在
operator[]
将搜索为重点,插入一个默认的构造,如果没有找到值,并返回到您指定的值的参考。显然,如果mapped_type
可以从直接初始化而不是默认构造和分配中受益,那么这可能是低效的。这种方法也使得无法确定是否确实发生了插入,或者您是否只覆盖了先前插入的键的值 insert
如果键已经存在于映射中,则成员函数将不起作用,尽管它经常被遗忘,但会返回一个std::pair<iterator, bool>
可能感兴趣的成员函数(最显着的是确定插入是否已实际完成)。
From all the listed possibilities to call insert
, all three are almostequivalent. As a reminder, let's have look at insert
signature in the standard :
从所有列出的调用可能性来看insert
,这三个几乎是等价的。提醒一下,让我们看看insert
标准中的签名:
typedef pair<const Key, T> value_type;
/* ... */
pair<iterator, bool> insert(const value_type& x);
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.
std::make_pair
依赖于模板参数推导,并且可能(在这种情况下将)产生value_type
与映射实际类型不同的类型,这将需要额外调用std::pair
模板构造函数以转换为value_type
(即:添加const
到first_type
)std::pair<int, int>
还需要对模板构造函数的额外调用std::pair
,以便将参数转换为value_type
(即:添加const
到first_type
)std::map<int, int>::value_type
绝对没有任何疑问,因为它直接是insert
成员函数所期望的参数类型。
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 the mapped_type
, and that I don't care about determining if a new key has effectively inserted. When using insert
, constructing a value_type
is probably the way to go.
最后,operator[]
当目标是插入时,我会避免使用,除非在默认构造和分配 时没有额外成本mapped_type
,并且我不关心确定新键是否已有效插入。使用时insert
,构造 avalue_type
可能是要走的路。
回答by Geoff Romer
As of C++11, you have two major additional options. First, you can use insert()
with list initialization syntax:
从 C++11 开始,您有两个主要的附加选项。首先,您可以使用insert()
with 列表初始化语法:
function.insert({0, 42});
This is functionally equivalent to
这在功能上等同于
function.insert(std::map<int, int>::value_type(0, 42));
but much more concise and readable. As other answers have noted, this has several advantages over the other forms:
但更简洁和可读。正如其他答案所指出的,这比其他形式有几个优点:
- The
operator[]
approach requires the mapped type to be assignable, which isn't always the case. - The
operator[]
approach can overwrite existing elements, and gives you no way to tell whether this has happened. - The other forms of
insert
that you list involve an implicit type conversion, which may slow your code down.
- 该
operator[]
方法要求映射类型是可分配的,但情况并非总是如此。 - 该
operator[]
方法可以覆盖现有元素,并且无法判断是否发生了这种情况。 insert
您列出的其他形式涉及隐式类型转换,这可能会降低您的代码速度。
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.
主要的缺点是这种形式过去要求键和值是可复制的,因此它不适用于例如带有unique_ptr
值的映射。这已在标准中修复,但修复可能尚未达到您的标准库实现。
Second, you can use the emplace()
method:
其次,您可以使用以下emplace()
方法:
function.emplace(0, 42);
This is more concise than any of the forms of insert()
, works fine with move-only types like unique_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, since emplace
methods aren't usually used that way.
这比 的任何形式都更简洁insert()
,适用于仅移动类型,如unique_ptr
,并且理论上可能稍微更有效(尽管体面的编译器应该优化差异)。唯一的主要缺点是它可能会让您的读者感到惊讶,因为emplace
通常不会以这种方式使用方法。
回答by In silico
The first version:
第一个版本:
function[0] = 42; // version 1
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.
可能会也可能不会将值 42 插入到地图中。如果键0
存在,那么它将为该键分配 42,覆盖该键具有的任何值。否则,它会插入键/值对。
The insert functions:
插入函数:
function.insert(std::map<int, int>::value_type(0, 42)); // version 2
function.insert(std::pair<int, int>(0, 42)); // version 3
function.insert(std::make_pair(0, 42)); // version 4
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.
另一方面,如果键0
已存在于地图中,则不要执行任何操作。如果键不存在,则插入键/值对。
The three insert functions are almost identical. std::map<int, int>::value_type
is the typedef
for std::pair<const int, int>
, and std::make_pair()
obviously produces a std::pair<>
via template deduction magic. The end result, however, should be the same for versions 2, 3, and 4.
三个插入功能几乎相同。std::map<int, int>::value_type
是typedef
for std::pair<const int, int>
,std::make_pair()
显然产生了一个std::pair<>
via 模板演绎魔法。但是,版本 2、3 和 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 factoway of inserting key/value pairs into a std::map
.
我会使用哪一种?我个人更喜欢版本 1;它简洁而“自然”。当然,如果不需要它的覆盖行为,那么我更喜欢第 4 版,因为它比第 2 版和第 3 版需要更少的输入。我不知道是否有一种事实上的方法可以将键/值对插入到std::map
.
Another way to insert values into a map via one of its constructors:
另一种通过其构造函数将值插入映射的方法:
std::map<int, int> quadratic_func;
quadratic_func[0] = 0;
quadratic_func[1] = 1;
quadratic_func[2] = 4;
quadratic_func[3] = 9;
std::map<int, int> my_func(quadratic_func.begin(), quadratic_func.end());
回答by honk
Since C++17std::map
offers two new insertion methods: insert_or_assign()
and try_emplace()
, as also mentioned in the comment by sp2danny.
由于C++17std::map
提供了两种新的插入方法:insert_or_assign()
and try_emplace()
,正如sp2danny的评论中也提到的。
insert_or_assign()
insert_or_assign()
Basically, insert_or_assign()
is an "improved" version of operator[]
. In contrast to operator[]
, insert_or_assign()
doesn't require the map's value type to be default constructible. For example, the following code doesn't compile, because MyClass
does not have a default constructor:
基本上,insert_or_assign()
是operator[]
. 与 相比operator[]
,insert_or_assign()
不要求地图的值类型是默认可构造的。例如,以下代码无法编译,因为MyClass
没有默认构造函数:
class MyClass {
public:
MyClass(int i) : m_i(i) {};
int m_i;
};
int main() {
std::map<int, MyClass> myMap;
// VS2017: "C2512: 'MyClass::MyClass' : no appropriate default constructor available"
// Coliru: "error: no matching function for call to 'MyClass::MyClass()"
myMap[0] = MyClass(1);
return 0;
}
However, if you replace myMap[0] = MyClass(1);
by the following line, then the code compiles and the insertion takes place as intended:
但是,如果您替换myMap[0] = MyClass(1);
为以下行,则代码将编译并按预期进行插入:
myMap.insert_or_assign(0, MyClass(1));
Moreover, similar to insert()
, insert_or_assign()
returns a pair<iterator, bool>
. The Boolean value is true
if an insertion occurred and false
if an assignment was done. The iterator points to the element that was inserted or updated.
此外,类似于insert()
,insert_or_assign()
返回一个pair<iterator, bool>
。布尔值是true
是否发生插入以及false
是否已完成分配。迭代器指向插入或更新的元素。
try_emplace()
try_emplace()
Similar to the above, try_emplace()
is an "improvement" of emplace()
. In contrast to emplace()
, 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 *):
与上述类似,try_emplace()
是对 的“改进” emplace()
。相反emplace()
,try_emplace()
如果由于映射中已存在的键而导致插入失败,则不会修改其参数。例如,以下代码尝试使用已存储在地图中的键放置元素(请参阅 *):
int main() {
std::map<int, std::unique_ptr<MyClass>> myMap2;
myMap2.emplace(0, std::make_unique<MyClass>(1));
auto pMyObj = std::make_unique<MyClass>(2);
auto [it, b] = myMap2.emplace(0, std::move(pMyObj)); // *
if (!b)
std::cout << "pMyObj was not inserted" << std::endl;
if (pMyObj == nullptr)
std::cout << "pMyObj was modified anyway" << std::endl;
else
std::cout << "pMyObj.m_i = " << pMyObj->m_i << std::endl;
return 0;
}
Output (at least for VS2017 and Coliru):
输出(至少对于 VS2017 和 Coliru):
pMyObj was not inserted
pMyObj was modified anyway
未插入
pMyObj 无论如何都修改了 pMyObj
As you can see, pMyObj
no longer points to the original object. However, if you replace auto [it, b] = myMap2.emplace(0, std::move(pMyObj));
by the the following code, then the output looks different, because pMyObj
remains unchanged:
如您所见,pMyObj
不再指向原始对象。但是,如果替换auto [it, b] = myMap2.emplace(0, std::move(pMyObj));
为以下代码,则输出看起来不同,因为pMyObj
保持不变:
auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));
Output:
输出:
pMyObj was not inserted
pMyObj pMyObj.m_i = 2
pMyObj 未插入
pMyObj pMyObj.m_i = 2
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 articleon Fluent C++.
请注意:我试图让我的解释尽可能简短和简单,以适应这个答案。要获得更精确和全面的描述,我建议阅读有关Fluent C++ 的这篇文章。
回答by user3116431
I have been running some time comparisons between the abovementioned versions:
我一直在对上述版本进行一些时间比较:
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));
Turns out that time differences between the insert versions are tiny.
事实证明,插入版本之间的时间差异很小。
#include <map>
#include <vector>
#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;
class Widget {
public:
Widget() {
m_vec.resize(100);
for(unsigned long it = 0; it < 100;it++) {
m_vec[it] = 1.0;
}
}
Widget(double el) {
m_vec.resize(100);
for(unsigned long it = 0; it < 100;it++) {
m_vec[it] = el;
}
}
private:
std::vector<double> m_vec;
};
int main(int argc, char* argv[]) {
std::map<int,Widget> map_W;
ptime t1 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
}
ptime t2 = boost::posix_time::microsec_clock::local_time();
time_duration diff = t2 - t1;
std::cout << diff.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_2;
ptime t1_2 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_2.insert(std::make_pair(it,Widget(2.0)));
}
ptime t2_2 = boost::posix_time::microsec_clock::local_time();
time_duration diff_2 = t2_2 - t1_2;
std::cout << diff_2.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_3;
ptime t1_3 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_3[it] = Widget(2.0);
}
ptime t2_3 = boost::posix_time::microsec_clock::local_time();
time_duration diff_3 = t2_3 - t1_3;
std::cout << diff_3.total_milliseconds() << std::endl;
std::map<int,Widget> map_W_0;
ptime t1_0 = boost::posix_time::microsec_clock::local_time();
for(int it = 0; it < 10000;it++) {
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
}
ptime t2_0 = boost::posix_time::microsec_clock::local_time();
time_duration diff_0 = t2_0 - t1_0;
std::cout << diff_0.total_milliseconds() << std::endl;
system("pause");
}
This gives respectively for the versions (I ran the file 3 times, hence the 3 consecutive time differences for each):
这分别给出了版本(我运行了 3 次文件,因此每个版本有 3 个连续的时间差):
map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
2198 ms, 2078 ms, 2072 ms
2198 毫秒、2078 毫秒、2072 毫秒
map_W_2.insert(std::make_pair(it,Widget(2.0)));
2290 ms, 2037 ms, 2046 ms
2290 毫秒、2037 毫秒、2046 毫秒
map_W_3[it] = Widget(2.0);
2592 ms, 2278 ms, 2296 ms
2592 毫秒、2278 毫秒、2296 毫秒
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
2234 ms, 2031 ms, 2027 ms
2234 毫秒、2031 毫秒、2027 毫秒
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.
map_W_3[it] = Widget(2.0);
由于使用 Widget 的默认构造函数进行初始化,此示例的版本需要多花 10-15% 的时间。
回答by Viktor Sehr
If you want to overwrite the element with key 0
如果你想用键 0 覆盖元素
function[0] = 42;
Otherwise:
除此以外:
function.insert(std::make_pair(0, 42));
回答by galactica
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, while insert()
is more efficient for adding values.
简而言之,[]
运算符更新值的效率更高,因为它涉及调用值类型的默认构造函数,然后为其分配一个新值,而insert()
添加值的效率更高。
The quoted snippet from Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Libraryby Scott Meyers, Item 24 might help.
引用有效 STL 的片段:改进标准模板库使用的 50 种特定方法,由 Scott Meyers,第 24 项可能有所帮助。
template<typename MapType, typename KeyArgType, typename ValueArgType>
typename MapType::iterator
insertKeyAndValue(MapType& m, const KeyArgType&k, const ValueArgType& v)
{
typename MapType::iterator lb = m.lower_bound(k);
if (lb != m.end() && !(m.key_comp()(k, lb->first))) {
lb->second = v;
return lb;
} else {
typedef typename MapType::value_type MVT;
return m.insert(lb, MVT(k, v));
}
}
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.
您可能会决定选择一个无通用编程的版本,但重点是我发现这个范式(区分“添加”和“更新”)非常有用。
回答by jo_
I just change the problem a little bit (map of strings) to show another interest of insert:
我只是稍微改变了问题(字符串映射)以显示插入的另一个兴趣:
std::map<int, std::string> rancking;
rancking[0] = 42; // << some compilers [gcc] show no error
rancking.insert(std::pair<int, std::string>(0, 42));// always a compile error
the fact that compiler shows no error on "rancking[1] = 42;" can have devastating impact !
编译器在“ranking[1] = 42;”上没有显示错误这一事实 会造成毁灭性的影响!
回答by Denis Shevchenko
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[].
如果您想在 std::map 中插入元素 - 使用 insert() 函数,并且如果您想(按键)查找元素并为其分配一些元素 - 使用运算符 []。
For simplify inserting use boost::assign library, like this:
为了简化插入使用 boost::assign 库,像这样:
using namespace boost::assign;
// For inserting one element:
insert( function )( 0, 41 );
// For inserting several elements:
insert( function )( 0, 41 )( 0, 42 )( 0, 43 );