148

我已经确定了四种将元素插入 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));

其中哪一个是首选/惯用方式?(还有其他我没有想到的方法吗?)

4

9 回答 9

126

从 C++11 开始,您有两个主要的附加选项。首先,您可以使用insert()with 列表初始化语法:

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方法通常不是这样使用的。

于 2013-11-13T00:21:00.280 回答
102

首先,operator[]成员insert函数在功能上并不等效:

  • 搜索operator[]键,如果未找到则插入默认构造值,并返回您为其分配值的引用。显然,如果可以从直接初始化而不是默认构造和分配中受益,那么这可能是低效的。此方法还无法确定是否确实发生了插入,或者您是否只覆盖了先前插入的键的值mapped_type
  • 如果键已经存在于映射中,则成员函数将insert不起作用,尽管它经常被遗忘,但返回一个std::pair<iterator, bool>可能感兴趣的值(最明显的是确定插入是否已实际完成)。

从所有列出的 call 可能性来看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(即:添加constfirst_type
  • std::pair<int, int>还需要对模板构造函数的额外调用std::pair才能将参数转换为value_type(即:添加constfirst_type
  • std::map<int, int>::value_type绝对没有疑问,因为它直接是insert成员函数所期望的参数类型。

最后,我会避免使用operator[]when 目标是插入,除非在默认构造和分配中没有额外的成本mapped_type,并且我不关心确定是否有效插入了新密钥。使用时insert,构造 avalue_type可能是要走的路。

于 2010-11-26T16:19:27.697 回答
13

第一个版本:

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_typetypedeffor std::pair<const int, int>std::make_pair()显然会产生一个std::pair<>通过模板推导魔术。但是,版本 2、3 和 4 的最终结果应该相同。

我会使用哪一个?我个人更喜欢版本 1;它简洁而“自然”。当然,如果不需要它的覆盖行为,那么我更喜欢版本 4,因为它比版本 2 和 3 需要更少的输入。我不知道是否存在将/值对插入到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());
于 2010-11-26T15:53:56.283 回答
10

由于C++17 std::map提供了两种新的插入方法:insert_or_assign()try_emplace(),正如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()返回 a 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

Coliru 上的代码

请注意:我试图让我的解释尽可能简短和简单,以使它们适合这个答案。要获得更准确和全面的描述,我建议阅读Fluent C++上的这篇文章

于 2019-09-26T06:47:05.640 回答
5

如果你想用键 0 覆盖元素

function[0] = 42;

除此以外:

function.insert(std::make_pair(0, 42));
于 2010-11-26T15:53:12.760 回答
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 毫秒、2078 毫秒、2072 毫秒

map_W_2.insert(std::make_pair(it,Widget(2.0)));

2290 毫秒、2037 毫秒、2046 毫秒

 map_W_3[it] = Widget(2.0);

2592 毫秒、2278 毫秒、2296 毫秒

 map_W_0.insert(std::map<int,Widget>::value_type(it,Widget(2.0)));

2234 毫秒、2031 毫秒、2027 毫秒

因此,可以忽略不同插入版本之间的结果(虽然没有执行假设检验)!

map_W_3[it] = Widget(2.0);由于使用 Widget 的默认构造函数进行初始化,此示例的版本花费了大约 10-15% 的时间。

于 2014-01-13T15:43:13.923 回答
3

简而言之,[]运算符更新值更有效,因为它涉及调用值类型的默认构造函数,然后为其分配一个新值,而insert()添加值更有效。

从有效 STL 中引用的片段: Scott Meyers的 50 种特定方法来改进您对标准模板库的使用,第 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));
    }
}

您可能会决定选择一个没有通用编程的版本,但关键是我发现这个范例(区分“添加”和“更新”)非常有用。

于 2017-03-02T18:58:07.390 回答
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 );
于 2010-11-26T18:32:34.630 回答
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

编译器在“ranking[1] = 42;”上没有显示错误的事实 可以产生毁灭性的影响!

于 2016-05-28T11:13:11.067 回答