插入地图的首选/惯用方式
我已经确定了插入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));
哪一种是首选/惯用的方式? (还有没有想过的另一种方式?)
首先, operator[]
和insert
成员函数在function上并不等同:
-
operator[]
将search该键,如果找不到,则插入一个默认的构造值,并返回一个给其赋值的引用。 显然,如果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
成员函数所期望的参数types。
最后,当目标是插入时,我会避免使用operator[]
,除非默认情况下没有额外的成本 – 构造和分配mapped_type
,并且我不关心确定新密钥是否已经有效插入。 当使用insert
,构build一个value_type
可能是一种方法。
从C ++ 11开始,你有两个主要的附加选项。 首先,你可以使用insert()
和list初始化语法:
function.insert({0, 42});
这在function上等同于
function.insert(std::map<int, int>::value_type(0, 42));
但更简洁可读。 正如其他答案所指出的那样,这与其他forms相比有几个优点:
-
operator[]
方法要求映射types是可分配的,但情况并非总是如此。 -
operator[]
方法可以覆盖现有元素,并且无法分辨是否发生了这种情况。 - 您列出的
insert
的其他forms涉及隐式types转换,这可能会减慢您的代码。
主要的缺点是,这种forms用来要求键和值是可复制的,所以它不适用于例如具有unique_ptr
值的映射。 标准中已经修复了这个问题,但是修正可能还没有达到你的标准库实现。
其次,你可以使用emplace()
方法:
function.emplace(0, 42);
这比insert()
任何forms都要简洁,对于像unique_ptr
这样的只移动types来说工作得很好,理论上可能稍微有效一些(尽pipe体面的编译器应该优化差异)。 唯一的缺点是可能会让读者感到惊讶,因为emplace
方法通常不是这样使用的。
第一个版本:
function[0] = 42; // version 1
可能会或可能不会将值42插入到地图中。 如果密钥0
存在,那么它将分配42到该密钥,覆盖该密钥所具有的任何值。 否则,它插入键/值对。
插入function:
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
已经存在于地图中,则不做任何事情。 如果密钥不存在,则插入密钥/值对。
三个插入function几乎完全相同。 std::map<int, int>::value_type
是std::pair<const int, int>
和std::make_pair()
显然通过模板演绎魔术产生std::pair<>
。 然而,最终的结果应该是相同的版本2,3和4。
我会用哪一个? 我个人比较喜欢版本1; 它简洁而“自然”。 当然,如果它的覆盖行为是不需要的,那么我宁愿使用版本4,因为它比版本2和3需要更less的input。我不知道是否有一个事实上的方法将键/值对插入到std::map
。
另一种通过其构造函数将值插入到地图中的方法:
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());
我已经在上述版本之间进行了一些比较:
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毫秒,2078毫秒,2072毫秒
map_W_2.insert(std::make_pair(it,Widget(2.0)));
2290ms,2037ms,2046ms
map_W_3[it] = Widget(2.0);
2592毫秒,2278毫秒,2296毫秒
map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));
2234ms,2031ms,2027ms
因此,不同插入版本之间的结果可以忽略不计(虽然没有进行假设检验)!
map_W_3[it] = Widget(2.0);
由于使用Widget的默认构造函数进行初始化,因此该示例需要大约10-15%的时间。
如果你想用键0覆盖元素
function[0] = 42;
除此以外:
function.insert(std::make_pair(0, 42));
如果要在std :: map中插入元素 – 使用insert()函数,并且如果要查找元素(通过键)并为其指定一些元素,请使用运算符[]。
为了简化插入使用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 );
简而言之, []
运算符更新值的效率更高,因为它涉及调用值types的默认构造函数,然后为其赋值一个新值,而insert()
则更有效地添加值。
斯科特·迈耶斯(Scott Meyers)提出的“ Effective STL:50种改善标准模板库使用的具体方法” (Item 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)); } }
你可能决定select一个通用编程的免费版本,关键是我发现这个范例(区分'add'和'update')非常有用。
我只是稍微改变一点问题(string图)来显示插入的另一个兴趣:
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;”上显示没有错误的事实 会产生毁灭性的影响!