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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 15:02:31  来源:igfitidea点击:

What is the preferred/idiomatic way to insert into a map?

c++stlinsertstdmapstd-pair

提问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 insertmember 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 the mapped_typecan 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 insertmember function will have no effect if the key is already present in the map and, although it is often forgotten, returns an std::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 insertsignature 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_pairrelies on template argument deduction and could (and in this case will) produce something of a different type than the actual value_typeof the map, which will require an additional call to std::pairtemplate constructor in order to convert to value_type(ie : adding constto first_type)
  • std::pair<int, int>will also require an additional call to the template constructor of std::pairin order to convert the parameter to value_type(ie : adding constto first_type)
  • std::map<int, int>::value_typeleaves absolutely no place for doubt as it is directly the parameter type expected by the insertmember function.
  • 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成员函数所期望的参数类型。

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_typeis 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 insertthat 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_ptrvalues. 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 emplacemethods 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 0exists, 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 0already 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_typeis the typedeffor 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_typetypedeffor 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::mapoffers 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 MyClassdoes 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 trueif an insertion occurred and falseif 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, pMyObjno 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 pMyObjremains 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

Code on Coliru

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 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 );