我已经确定了四种不同的方式插入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));
其中哪些是首选/惯用方式? (还有另一种我没想过的方法吗?)
答案 0 :(得分:80)
从C ++ 11开始,您有两个主要的附加选项。首先,您可以将insert()
与列表初始化语法一起使用:
function.insert({0, 42});
这在功能上等同于
function.insert(std::map<int, int>::value_type(0, 42));
但更简洁,更易读。正如其他答案所指出的,这比其他形式有几个优点:
operator[]
方法要求映射类型可分配,但情况并非总是如此。operator[]
方法可以覆盖现有元素,并且无法判断是否发生这种情况。insert
形式涉及隐式类型转换,这可能会降低您的代码速度。主要缺点是这种形式用于要求密钥和值是可复制的,因此它不适用于例如具有unique_ptr
值的地图。这已在标准中修复,但修复程序可能尚未达到标准库实现。
其次,您可以使用emplace()
方法:
function.emplace(0, 42);
这比insert()
的任何形式都更简洁,适用于仅unique_ptr
等仅移动类型,理论上可能稍微更有效(尽管一个不错的编译器应该优化差异)。唯一的主要缺点是它可能会给您的读者带来一些惊喜,因为emplace
方法通常不会以这种方式使用。
答案 1 :(得分:76)
首先,operator[]
和insert
成员函数在功能上并不相同:
operator[]
将搜索以获取密钥,如果未找到,则插入默认构造的值,并返回您为其指定值的引用。显然,如果mapped_type
可以从直接初始化而不是默认构造和分配中受益,那么这可能是低效的。此方法还使得无法确定是否确实发生了插入,或者您是否仅覆盖了先前插入的键的值insert
成员函数将无效,虽然经常被遗忘,但返回std::pair<iterator, bool>
这可能是有意义的(最值得注意的是确定是否插入实际上已经完成了。)从所有列出的可能性中调用insert
,这三个几乎等效。提醒一下,让我们看一下标准中的insert
签名:
typedef pair<const Key, T> value_type;
/* ... */
pair<iterator, bool> insert(const value_type& x);
那么这三个电话有何不同?
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
成员函数所期望的参数类型。最后,我会避免在插入目标时使用operator[]
,除非默认构建和分配mapped_type
没有额外费用,并且我不关心确定新密钥是否已有效插入。使用insert
时,构建value_type
可能是最佳选择。
答案 2 :(得分:7)
第一个版本:
function[0] = 42; // version 1
可能会也可能不会将值42插入地图中。如果密钥0
存在,那么它将为该密钥分配42,覆盖该密钥具有的任何值。否则它会插入键/值对。
插入功能:
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
另一方面,如果地图中已存在密钥0
,则不要执行任何操作。如果密钥不存在,则插入密钥/值对。
三个插入功能几乎相同。 std::map<int, int>::value_type
是typedef
的{{1}},而std::pair<const int, int>
显然会通过模板扣除魔法产生std::make_pair()
。但是,对于版本2,3和4,最终结果应该相同。
我会使用哪一个?我个人更喜欢第1版;它简洁而“自然”。当然,如果不需要覆盖行为,那么我更喜欢版本4,因为它比版本2和3需要更少的输入。我不知道是否有一个事实上的方式将键/值对插入std::pair<>
。
通过其构造函数之一将值插入到地图中的另一种方法:
std::map
答案 3 :(得分:3)
我在上述版本之间进行了一些时间比较:
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));
原来,插入版本之间的时差很小。
#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");
}
这分别给出了版本(我运行了3次文件,因此每次连续3次):
map_W.insert(std::pair<int,Widget>(it,Widget(2.0)));
2198 ms,2078 ms,2072 ms
map_W_2.insert(std::make_pair(it,Widget(2.0)));
2290 ms,2037 ms,2046 ms
map_W_3[it] = Widget(2.0);
2592 ms,2278 ms,2296 ms
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
2234 ms,2031 ms,2027 ms
因此,可以忽略不同插入版本之间的结果(尽管没有进行假设检验)!
由于使用Widget的默认构造函数进行初始化,map_W_3[it] = Widget(2.0);
版本对此示例的时间大约多10-15%。
答案 4 :(得分:2)
由于C++17 std::map
提供了两种新的插入方法:insert_or_assign()
和try_emplace()
,正如comment by sp2danny中所述。
insert_or_assign()
基本上,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;
}
但是,如果将myMap[0] = MyClass(1);
替换为以下行,则代码将编译并按预期进行插入:
myMap.insert_or_assign(0, MyClass(1));
此外,类似于insert()
,insert_or_assign()
返回pair<iterator, bool>
。如果发生插入,则布尔值为true
,如果完成分配,则布尔值为false
。迭代器指向已插入或更新的元素。
try_emplace()
与上述类似,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;
}
输出(至少对于VS2017和Coliru):
未插入pMyObj
pMyObj还是被修改了
如您所见,pMyObj
不再指向原始对象。但是,如果用以下代码替换auto [it, b] = myMap2.emplace(0, std::move(pMyObj));
,则输出看起来会有所不同,因为pMyObj
保持不变:
auto [it, b] = myMap2.try_emplace(0, std::move(pMyObj));
输出:
未插入pMyObj
pMyObj pMyObj.m_i = 2
请注意:我试图使我的解释尽可能简短,以使它们适合此答案。有关更精确,更全面的描述,我建议阅读 Fluent C ++ 上的this article。
答案 5 :(得分:1)
如果要用键0覆盖元素
function[0] = 42;
否则:
function.insert(std::make_pair(0, 42));
答案 6 :(得分:1)
如果要在std :: map中插入元素 - 使用insert()函数,如果要查找元素(按键)并为其指定一些元素,请使用operator []。
为简化插入,请使用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 );
答案 7 :(得分:1)
我只是稍微改变了一下这个问题(字符串的映射)以显示插入的另一个兴趣:
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
编译器在“rancking [1] = 42;”上没有显示错误这一事实会产生毁灭性的影响!
答案 8 :(得分:1)
简而言之,[]
运算符更有效地更新值,因为它涉及调用值类型的默认构造函数,然后为其分配新值,而insert()
更有效地添加值。
来自有效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));
}
}
你可能决定选择一个免费的通用编程版本,重点是我发现这个范例(区分&#39;添加&#39;更新&#39;)非常有用。