在 C++ 地图中插入 vs emplace vs operator[]

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17172080/
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-27 20:58:32  来源:igfitidea点击:

insert vs emplace vs operator[] in c++ map

c++dictionaryinsertoperatorsemplace

提问by German Capuano

I'm using maps for the first time and I realized that there are many ways to insert an element. You can use emplace(), operator[]or insert(), plus variants like using value_typeor make_pair. While there is a lot of information about all of them and questions about particular cases, I still can't understand the big picture. So, my two questions are:

我第一次使用地图,我意识到有很多方法可以插入元素。您可以使用emplace(),operator[]insert(),加上使用value_type或等变体make_pair。虽然有很多关于所有这些的信息和关于特定案例的问题,但我仍然无法理解大局。所以,我的两个问题是:

  1. What is the advantage of each one of them over the others?

  2. Was there any need for adding emplace to the standard? Is there anything that wasn't possible before without it?

  1. 他们每个人相对于其他人的优势是什么?

  2. 是否需要在标准中添加 emplace?没有它,以前有什么是不可能的吗?

回答by David Rodríguez - dribeas

In the particular case of a map the old options were only two: operator[]and insert(different flavors of insert). So I will start explaining those.

在地图的特殊情况下,旧选项只有两个:operator[]insert(不同风格的insert)。所以我将开始解释这些。

The operator[]is a find-or-addoperator. It will try to find an element with the given key inside the map, and if it exists it will return a reference to the stored value. If it does not, it will create a new element inserted in place with default initialization and return a reference to it.

operator[]是一个发现-或-加运算符。它将尝试在地图中找到具有给定键的元素,如果存在,它将返回对存储值的引用。如果没有,它将创建一个新元素插入到位并使用默认初始化并返回对它的引用。

The insertfunction (in the single element flavor) takes a value_type(std::pair<const Key,Value>), it uses the key (firstmember) and tries to insert it. Because std::mapdoes not allow for duplicates if there is an existing element it will not insert anything.

insert函数(在单元素风格中)接受一个value_type( std::pair<const Key,Value>),它使用键 ( firstmember) 并尝试插入它。因为std::map如果存在现有元素,则不允许重复,所以它不会插入任何内容。

The first difference between the two is that operator[]needs to be able to construct a default initialized value, and it is thus unusable for value types that cannot be default initialized. The second difference between the two is what happens when there is already an element with the given key. The insertfunction will not modify the state of the map, but instead return an iterator to the element (and a falseindicating that it was not inserted).

两者的第一个区别是operator[]需要能够构造一个默认的初始化,因此对于不能默认初始化的值类型是不可用的。两者之间的第二个区别是当已经存在具有给定键的元素时会发生什么。该insert函数不会修改地图的状态,而是返回一个迭代器到元素(并false指示它没有被插入)。

// assume m is std::map<int,int> already has an element with key 5 and value 0
m[5] = 10;                      // postcondition: m[5] == 10
m.insert(std::make_pair(5,15)); // m[5] is still 10

In the case of insertthe argument is an object of value_type, which can be created in different ways. You can directly construct it with the appropriate type or pass any object from which the value_typecan be constructed, which is where std::make_paircomes into play, as it allows for simple creation of std::pairobjects, although it is probably not what you want...

insert参数是 的对象的情况下value_type,可以用不同的方式创建。您可以使用适当的类型直接构造它或传递value_type可以从中构造它的任何对象,这是std::make_pair发挥作用的地方,因为它允许简单地创建std::pair对象,尽管它可能不是您想要的......

The net effect of the following calls is similar:

以下调用的净效果类似

K t; V u;
std::map<K,V> m;           // std::map<K,V>::value_type is std::pair<const K,V>

m.insert( std::pair<const K,V>(t,u) );      // 1
m.insert( std::map<K,V>::value_type(t,u) ); // 2
m.insert( std::make_pair(t,u) );            // 3

But the are not really the same... [1] and [2] are actually equivalent. In both cases the code creates a temporary object of the same type (std::pair<const K,V>) and passes it to the insertfunction. The insertfunction will create the appropriate node in the binary search tree and then copy the value_typepart from the argument to the node. The advantage of using value_typeis that, well, value_typealways matchesvalue_type, you cannot mistype the type of the std::pairarguments!

但实际上并不相同...... [1] 和 [2] 实际上是等价的。在这两种情况下,代码都会创建一个相同类型的临时对象 ( std::pair<const K,V>) 并将其传递给insert函数。该insert函数将在二叉搜索树中创建适当的节点,然后将value_type参数中的部分复制到节点。使用的好处value_type是,value_type总是匹配value_type,你不能错误输入std::pair参数的类型!

The difference is in [3]. The function std::make_pairis a template function that will create a std::pair. The signature is:

区别在于[3]。该函数std::make_pair是一个模板函数,它将创建一个std::pair. 签名是:

template <typename T, typename U>
std::pair<T,U> make_pair(T const & t, U const & u );

I have intentionally not provided the template arguments to std::make_pair, as that is the common usage. And the implication is that the template arguments are deduced from the call, in this case to be T==K,U==V, so the call to std::make_pairwill return a std::pair<K,V>(note the missing const). The signature requires value_typethat is closebut not the same as the returned value from the call to std::make_pair. Because it is close enough it will create a temporary of the correct type and copy initialize it. That will in turn be copied to the node, creating a total of two copies.

我故意不向 提供模板参数std::make_pair,因为这是常见用法。这意味着模板参数是从调用中推导出来的,在本例中为T==K,U==V,因此调用std::make_pair将返回 a std::pair<K,V>(注意缺少的const)。签名要求value_type接近,但不一样的从调用返回的值std::make_pair。因为它足够接近,它会创建一个正确类型的临时文件并复制初始化它。这将依次复制到节点,共创建两个副本。

This can be fixed by providing the template arguments:

这可以通过提供模板参数来解决:

m.insert( std::make_pair<const K,V>(t,u) );  // 4

But that is still error prone in the same way that explicitly typing the type in case [1].

但这仍然容易出错,就像在 case [1] 中显式键入类型一样。

Up to this point, we have different ways of calling insertthat require the creation of the value_typeexternally and the copy of that object into the container. Alternatively you can use operator[]if the type is default constructibleand assignable(intentionally focusing only in m[k]=v), and it requires the default initialization of one object and the copyof the value into that object.

到目前为止,我们有不同的调用方式insert,需要在value_type外部创建对象并将该对象复制到容器中。或者,operator[]如果类型是默认可构造可分配的(有意仅关注m[k]=v),并且它需要一个对象的默认初始化并将值复制到该对象中,则您可以使用它。

In C++11, with variadic templates and perfect forwarding there is a new way of adding elements into a container by means of emplacing(creating in place). The emplacefunctions in the different containers do basically the same thing: instead of getting a sourcefrom which to copyinto the container, the function takes the parameters that will be forwarded to the constructor of the object stored in the container.

在 C++11 中,通过可变模板和完美转发,提供了一种通过emplaceing(就地创建)将元素添加到容器中的新方法。emplace不同容器中的函数做的事情基本相同:函数不是获取要复制到容器中的,而是获取将转发给存储在容器中的对象的构造函数的参数。

m.emplace(t,u);               // 5

In [5], the std::pair<const K, V>is not created and passed to emplace, but rather references to the tand uobject are passed to emplacethat forwards them to the constructor of the value_typesubobject inside the data structure. In this case nocopies of the std::pair<const K,V>are done at all, which is the advantage of emplaceover the C++03 alternatives. As in the case of insertit will not override the value in the map.

在 [5] 中,std::pair<const K, V>不是创建并传递给emplace,而是传递给tu对象的引用emplace,并将它们转发给value_type数据结构内的子对象的构造函数。在这种情况下,根本没有复制std::pair<const K,V>,这是emplace优于 C++03 替代方案的优势。在这种情况下,insert它不会覆盖地图中的值。



An interesting question that I had not thought about is how emplacecan actually be implemented for a map, and that is not a simple problem in the general case.

一个我没有想过的有趣问题是如何emplace为地图实际实现,这在一般情况下不是一个简单的问题。

回答by ChrisCM

Emplace: Takes advantage of the rvalue reference to use the actual objects that you have already created. This means that no copy or move constructor is called, good for LARGE objects! O(log(N)) time.

Emplace:利用右值引用来使用您已经创建的实际对象。这意味着不会调用复制或移动构造函数,这对 LARGE 对象有好处!O(log(N)) 时间。

Insert: Has overloads for standard lvalue reference and rvalue reference, as well as iterators to lists of elements to insert, and "hints" as to the position an element belongs. The use of a "hint" iterator can bring the time insertion takes down to contant time, otherwise it is O(log(N)) time.

插入:具有标准左值引用和右值引用的重载,以及要插入的元素列表的迭代器,以及元素所属位置的“提示”。使用“提示”迭代器可以将插入所需的时间缩短为恒定时间,否则为 O(log(N)) 时间。

Operator[]: Checks to see if the object exists, and if it does, modifies the reference to this object, otherwise uses the provided key and value to call make_pair on the two objects, and then does the same work as the insert function. This is O(log(N)) time.

Operator[]:检查对象是否存在,如果存在,则修改对这个对象的引用,否则使用提供的键和值在两个对象上调用make_pair,然后做和insert函数一样的工作。这是 O(log(N)) 时间。

make_pair: Does little more than make a pair.

make_pair: 只做一对。

There was no "need" for adding emplace to the standard. In c++11 I believe the && type of reference was added. This removed the necessity for move semantics, and allowed optimization of some specific type of memory management. In particular, the rvalue reference. The overloaded insert(value_type &&) operator does not take advantage of the in_place semantics, and is therefore much less efficient. While it provides the capability of dealing with rvalue references, it ignores their key purpose, which is in place construction of objects.

没有“需要”将 emplace 添加到标准中。在 c++11 中,我相信添加了 && 类型的引用。这消除了移动语义的必要性,并允许优化某些特定类型的内存管理。特别是右值引用。重载的 insert(value_type &&) 运算符没有利用 in_place 语义,因此效率低得多。虽然它提供了处理右值引用的能力,但它忽略了它们的关键目的,即对象的就地构造。

回答by Kerrek SB

Apart from the optimisation opportunities and the simpler syntax, an important distinction between insertion and emplacement is that the latter allows explicitconversions. (This is across the entire standard library, not just for maps.)

除了优化机会和更简单的语法之外,插入和定位之间的一个重要区别是后者允许显式转换。(这是整个标准库,而不仅仅是地图。)

Here's an example to demonstrate:

这是一个演示示例:

#include <vector>

struct foo
{
    explicit foo(int);
};

int main()
{
    std::vector<foo> v;

    v.emplace(v.end(), 10);      // Works
    //v.insert(v.end(), 10);     // Error, not explicit
    v.insert(v.end(), foo(10));  // Also works
}

This is admittedly a very specific detail, but when you're dealing with chains of user-defined conversions, it's worth keeping this in mind.

诚然,这是一个非常具体的细节,但是当您处理用户定义的转换链时,值得牢记这一点。

回答by Matthew K.

The following code may help you understand the "big picture idea" of how insert()differs from emplace():

以下代码可能会帮助您了解insert()emplace():

#include <iostream>
#include <unordered_map>
#include <utility>

//Foo simply outputs what constructor is called with what value.
struct Foo {
  static int foo_counter; //Track how many Foo objects have been created.
  int val; //This Foo object was the val-th Foo object to be created.

  Foo() { val = foo_counter++;
    std::cout << "Foo() with val:                " << val << '\n';
  }
  Foo(int value) : val(value) { foo_counter++;
    std::cout << "Foo(int) with val:             " << val << '\n';
  }
  Foo(Foo& f2) { val = foo_counter++;
    std::cout << "Foo(Foo &) with val:           " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(const Foo& f2) { val = foo_counter++;
    std::cout << "Foo(const Foo &) with val:     " << val
              << " \tcreated from:      \t" << f2.val << '\n';
  }
  Foo(Foo&& f2) { val = foo_counter++;
    std::cout << "Foo(Foo&&) moving:             " << f2.val
              << " \tand changing it to:\t" << val << '\n';
  }
  ~Foo() { std::cout << "~Foo() destroying:             " << val << '\n'; }

  Foo& operator=(const Foo& rhs) {
    std::cout << "Foo& operator=(const Foo& rhs) with rhs.val: " << rhs.val
              << " \tcalled with lhs.val = \t" << val
              << " \tChanging lhs.val to: \t" << rhs.val << '\n';
    val = rhs.val;
    return *this;
  }

  bool operator==(const Foo &rhs) const { return val == rhs.val; }
  bool operator<(const Foo &rhs)  const { return val < rhs.val;  }
};

int Foo::foo_counter = 0;

//Create a hash function for Foo in order to use Foo with unordered_map
namespace std {
   template<> struct hash<Foo> {
       std::size_t operator()(const Foo &f) const {
           return std::hash<int>{}(f.val);
       }
   };
}

int main()
{
    std::unordered_map<Foo, int> umap;  
    Foo foo0, foo1, foo2, foo3;
    int d;

    //Print the statement to be executed and then execute it.

    std::cout << "\numap.insert(std::pair<Foo, int>(foo0, d))\n";
    umap.insert(std::pair<Foo, int>(foo0, d));
    //Side note: equiv. to: umap.insert(std::make_pair(foo0, d));

    std::cout << "\numap.insert(std::move(std::pair<Foo, int>(foo1, d)))\n";
    umap.insert(std::move(std::pair<Foo, int>(foo1, d)));
    //Side note: equiv. to: umap.insert(std::make_pair(foo1, d));

    std::cout << "\nstd::pair<Foo, int> pair(foo2, d)\n";
    std::pair<Foo, int> pair(foo2, d);

    std::cout << "\numap.insert(pair)\n";
    umap.insert(pair);

    std::cout << "\numap.emplace(foo3, d)\n";
    umap.emplace(foo3, d);

    std::cout << "\numap.emplace(11, d)\n";
    umap.emplace(11, d);

    std::cout << "\numap.insert({12, d})\n";
    umap.insert({12, d});

    std::cout.flush();
}

The output that I got was:

我得到的输出是:

Foo() with val:                0
Foo() with val:                1
Foo() with val:                2
Foo() with val:                3

umap.insert(std::pair<Foo, int>(foo0, d))
Foo(Foo &) with val:           4    created from:       0
Foo(Foo&&) moving:             4    and changing it to: 5
~Foo() destroying:             4

umap.insert(std::move(std::pair<Foo, int>(foo1, d)))
Foo(Foo &) with val:           6    created from:       1
Foo(Foo&&) moving:             6    and changing it to: 7
~Foo() destroying:             6

std::pair<Foo, int> pair(foo2, d)
Foo(Foo &) with val:           8    created from:       2

umap.insert(pair)
Foo(const Foo &) with val:     9    created from:       8

umap.emplace(foo3, d)
Foo(Foo &) with val:           10   created from:       3

umap.emplace(11, d)
Foo(int) with val:             11

umap.insert({12, d})
Foo(int) with val:             12
Foo(const Foo &) with val:     13   created from:       12
~Foo() destroying:             12

~Foo() destroying:             8
~Foo() destroying:             3
~Foo() destroying:             2
~Foo() destroying:             1
~Foo() destroying:             0
~Foo() destroying:             13
~Foo() destroying:             11
~Foo() destroying:             5
~Foo() destroying:             10
~Foo() destroying:             7
~Foo() destroying:             9

Notice that:

请注意:

  1. An unordered_mapalways internally stores Fooobjects (and not, say, Foo *s) as keys, which are all destroyed when the unordered_mapis destroyed. Here, the unordered_map's internal keys were foos 13, 11, 5, 10, 7, and 9.

    • So technically, our unordered_mapactually stores std::pair<const Foo, int>objects, which in turn store the Fooobjects. But to understand the "big picture idea" of how emplace()differs from insert()(see highlighted box below), it's okay to temporarilyimagine this std::pairobject as being entirely passive. Once you understand this "big picture idea," it's important to then back up and understand how the use of this intermediary std::pairobject by unordered_mapintroduces subtle, but important, technicalities.
  2. Inserting each of foo0, foo1, and foo2required 2 calls to one of Foo's copy/move constructors and 2 calls to Foo's destructor (as I now describe):

    a. Inserting each of foo0and foo1created a temporary object (foo4and foo6, respectively) whose destructor was then immediately called after the insertion completed. In addition, the unordered_map's internal Foos (which are Foos 5 and 7) also had their destructors called when the unordered_map was destroyed.

    b. To insert foo2, we instead first explicitly created a non-temporary pair object (called pair), which called Foo's copy constructor on foo2(creating foo8as an internal member of pair). We then insert()ed this pair, which resulted in unordered_mapcalling the copy constructor again (on foo8) to create its own internal copy (foo9). As with foos 0 and 1, the end result was two destructor calls for this insertion with the only difference being that foo8's destructor was called only when we reached the end of main()rather than being called immediately after insert()finished.

  3. Emplacing foo3resulted in only 1 copy/move constructor call (creating foo10internally in the unordered_map) and only 1 call to Foo's destructor. (I'll get back to this later).

  4. For foo11, we directly passed the integer 11 to emplace(11, d)so that unordered_mapwould call the Foo(int)constructor while execution is within its emplace()method. Unlike in (2) and (3), we didn't even need some pre-exiting fooobject to do this. Importantly, notice that only 1 call to a Fooconstructor occurred (which created foo11).

  5. We then directly passed the integer 12 to insert({12, d}). Unlike with emplace(11, d)(which recall resulted in only 1 call to a Fooconstructor), this call to insert({12, d})resulted in two calls to Foo's constructor (creating foo12and foo13).

  1. Anunordered_map总是在内部将Foo对象(而不是,比如说,Foo *s)存储为键,当unordered_map被销毁时,所有这些都被销毁。这里,unordered_map的内部键是 foos 13、11、5、10、7 和 9。

    • 所以从技术上讲,我们unordered_map实际上存储了std::pair<const Foo, int>对象,而后者又存储了Foo对象。但是要理解emplace()insert()(见下面突出显示的框)有什么不同的“大局概念”,暂时将此std::pair对象想象为完全被动是可以的。一旦你理解了这个“大局理念”,重要的是要备份和理解这个中间std::pair对象的使用如何unordered_map引入微妙但重要的技术细节。
  2. 插入、 和 中的每一个foo0,需要对的复制/移动构造函数之一进行 2 次调用,对 的析构函数进行2 次调用(正如我现在所描述的):foo1foo2FooFoo

    一种。插入每个foo0foo1创建一个临时对象(foo4foo6,分别),然后在插入完成后立即调用其析构函数。此外,当 unordered_map 被销毁时,unordered_map 的内部Foos(即Foos 5 和 7)也调用了它们的析构函数。

    湾 为了插入foo2,我们首先显式地创建了一个非临时对对象(称为pair),它调用了Foo的复制构造函数foo2foo8作为 的内部成员创建pair)。然后我们insert()编辑了这对,这导致unordered_map再次调用复制构造函数(on foo8)来创建它自己的内部副本(foo9)。与foos 0 和 1 一样,最终结果是对这个插入进行了两次析构函数调用,唯一的区别是foo8只有当我们到达结束时才调用 的析构函数,main()而不是在insert()完成后立即调用。

  3. Emplacingfoo3仅导致 1 次复制/移动构造函数调用(在foo10内部创建unordered_map)和仅 1 次调用Foo的析构函数。(我稍后会回到这个话题)。

  4. 对于foo11,我们直接将整数 11 传递给它,emplace(11, d)以便在其方法内执行时unordered_map调用Foo(int)构造函数emplace()。与 (2) 和 (3) 不同,我们甚至不需要一些预先存在的foo对象来做到这一点。重要的是,请注意仅Foo发生了1 次对构造函数的调用(创建了foo11)。

  5. 然后我们直接将整数 12 传递给insert({12, d})。与 with emplace(11, d)(回想起来仅导致对Foo构造函数的1 次调用)不同,此对 的调用insert({12, d})导致对Foo的构造函数的两次调用(创建foo12foo13)。

This shows what the main "big picture" difference between insert()and emplace()is:

这显示了insert()和之间的主要“大图”区别emplace()是:

Whereas using insert()almost alwaysrequires the construction or existence of some Fooobject in main()'s scope (followed by a copy or move), if using emplace()then any call to a Fooconstructor is done entirely internally in the unordered_map(i.e. inside the scope of the emplace()method's definition). The argument(s) for the key that you pass to emplace()are directly forwarded to a Fooconstructor call within unordered_map::emplace()'s definition (optional additional details: where this newly constructed object is immediately incorporated into one of unordered_map's member variables so that no destructor is called when execution leaves emplace()and no move or copy constructors are called).

而 usinginsert()几乎总是需要Foomain()的范围内构造或存在某个对象(然后是复制或移动),如果使用,emplace()则对Foo构造函数的任何调用都完全在 内部完成unordered_map(即在emplace()方法定义的范围内)。您传递给的键的参数emplace()被直接转发到的定义中的Foo构造函数调用unordered_map::emplace()(可选的附加细节:这个新构造的对象被立即合并到unordered_map的成员变量之一,这样当执行离开emplace()并且不调用移动或复制构造函数)。

Note: The reason for the "almost" in "almost always" above is explained in I) below.

注意:上面“几乎总是”中“几乎”的原因在下面的I)中解释。

  1. continued: The reason why calling umap.emplace(foo3, d)called Foo's non-const copy constructor is the following: Since we're using emplace(), the compiler knows that foo3(a non-const Fooobject) is meant to be an argument to some Fooconstructor. In this case, the most fitting Fooconstructor is the non-const copy constructor Foo(Foo& f2). This is why umap.emplace(foo3, d)called a copy constructor while umap.emplace(11, d)did not.
  1. 继续:调用umap.emplace(foo3, d)被调用Foo的非常量复制构造函数的原因如下:由于我们正在使用emplace(),编译器知道foo3(非常量Foo对象)是某个Foo构造函数的参数。在这种情况下,最合适的Foo构造函数是非常量复制构造函数Foo(Foo& f2)。这就是为什么umap.emplace(foo3, d)调用复制构造函数而umap.emplace(11, d)没有调用的原因。

Epilogue:

结语:

I. Note that one overload of insert()is actually equivalent toemplace(). As described in this cppreference.com page, the overload template<class P> std::pair<iterator, bool> insert(P&& value)(which is overload (2) of insert()on this cppreference.com page) is equivalent to emplace(std::forward<P>(value)).

I. 请注意, 的一个重载insert()实际上等效于emplace()。如所描述的在此cppreference.com页,过载template<class P> std::pair<iterator, bool> insert(P&& value)(这是过载(2)insert()此cppreference.com页)等效于emplace(std::forward<P>(value))

II. Where to go from here?

二、然后去哪儿?

a. Play around with the above source code and study documentation for insert()(e.g. here) and emplace()(e.g. here) that's found online. If you're using an IDE such as eclipse or NetBeans then you can easily get your IDE to tell you which overload of insert()or emplace()is being called (in eclipse, just keep your mouse's cursor steady over the function call for a second). Here's some more code to try out:

一种。使用上面的源代码并学习在线找到的insert()(例如这里)和emplace()(例如这里)的文档。如果您使用的是 Eclipse 或 NetBeans 等 IDE,那么您可以轻松地让您的 IDE 告诉您哪个重载insert()emplace()正在被调用(在 eclipse 中,只需将鼠标光标保持在函数调用上一秒钟)。这里还有一些代码可以尝试:

std::cout << "\numap.insert({{" << Foo::foo_counter << ", d}})\n";
umap.insert({{Foo::foo_counter, d}});
//but umap.emplace({{Foo::foo_counter, d}}); results in a compile error!

std::cout << "\numap.insert(std::pair<const Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<const Foo, int>({Foo::foo_counter, d}));
//The above uses Foo(int) and then Foo(const Foo &), as expected. but the
// below call uses Foo(int) and the move constructor Foo(Foo&&). 
//Do you see why?
std::cout << "\numap.insert(std::pair<Foo, int>({" << Foo::foo_counter << ", d}))\n";
umap.insert(std::pair<Foo, int>({Foo::foo_counter, d}));
//Not only that, but even more interesting is how the call below uses all 
// three of Foo(int) and the Foo(Foo&&) move and Foo(const Foo &) copy 
// constructors, despite the below call's only difference from the call above 
// being the additional { }.
std::cout << "\numap.insert({std::pair<Foo, int>({" << Foo::foo_counter << ", d})})\n";
umap.insert({std::pair<Foo, int>({Foo::foo_counter, d})});


//Pay close attention to the subtle difference in the effects of the next 
// two calls.
int cur_foo_counter = Foo::foo_counter;
std::cout << "\numap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}}) where " 
  << "cur_foo_counter = " << cur_foo_counter << "\n";
umap.insert({{cur_foo_counter, d}, {cur_foo_counter+1, d}});

std::cout << "\numap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}}) where "
  << "Foo::foo_counter = " << Foo::foo_counter << "\n";
umap.insert({{Foo::foo_counter, d}, {Foo::foo_counter+1, d}});


//umap.insert(std::initializer_list<std::pair<Foo, int>>({{Foo::foo_counter, d}}));
//The call below works fine, but the commented out line above gives a 
// compiler error. It's instructive to find out why. The two calls
// differ by a "const".
std::cout << "\numap.insert(std::initializer_list<std::pair<const Foo, int>>({{" << Foo::foo_counter << ", d}}))\n";
umap.insert(std::initializer_list<std::pair<const Foo, int>>({{Foo::foo_counter, d}}));

You'll soon see that which overload of the std::pairconstructor (see reference) ends up being used by unordered_mapcan have an important effect on how many objects are copied, moved, created, and/or destroyed as well as when this all occurs.

您很快就会看到std::pair构造函数的哪个重载(请参阅参考资料)最终被使用unordered_map会对复制、移动、创建和/或销毁对象的数量以及这一切发生的时间产生重要影响。

b. See what happens when you use some other container class (e.g. std::setor std::unordered_multiset) instead of std::unordered_map.

湾 看看当您使用其他容器类(例如std::setstd::unordered_multiset)而不是std::unordered_map.

c. Now use a Gooobject (just a renamed copy of Foo) instead of an intas the range type in an unordered_map(i.e. use unordered_map<Foo, Goo>instead of unordered_map<Foo, int>) and see how many and which Gooconstructors are called. (Spoiler: there is an effect but it isn't very dramatic.)

C。现在使用Goo对象(只是 的重命名副本Foo)而不是 anint作为范围类型unordered_map(即使用unordered_map<Foo, Goo>而不是unordered_map<Foo, int>)并查看Goo调用了多少和哪些构造函数。(剧透:有效果,但不是很戏剧化。)

回答by frostcs

In terms of functionality or output, they are both same.

在功能或输出方面,它们都是相同的。

For both large memory, object emplace is memory-optimized which don't use copy constructors

对于大内存,对象 emplace 是内存优化的,不使用复制构造函数

For simple detailed explanation https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44

简单详细解释 https://medium.com/@sandywits/all-about-emplace-in-c-71fd15e06e44