以下代码可以帮助您理解与以下方面有何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();
}
我得到的输出是:
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
注意:
一个unordered_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引入微妙但重要的技术来使用此中介对象,这一点很重要。
将每个的foo0,foo1以及foo2需要2调用之一Foo的复制/移动的构造和图2点的调用Foo的析构函数(如我现在描述):
一个。插入foo0并foo1创建一个临时对象(分别为foo4和foo6),然后在完成插入后立即调用其析构函数。此外,在销毁unordered_map时,unordered_map的内部Foos(分别为Foo5和7)也调用了它们的析构函数。
b。要插入foo2,我们首先明确创建了一个非临时对对象(称为pair),该对象称为Foo的复制构造函数on foo2(foo8作为的内部成员创建pair)。然后insert(),我们编辑了这对,导致unordered_map再次(在上foo8)调用复制构造函数以创建其自己的内部副本(foo9)。与foos 0和1一样,最终结果是对该插入操作进行了两次析构函数调用,唯一的不同是,foo8只有在我们到达的末尾才调用的析构函数,main()而不是在insert()完成后立即调用。
嵌入foo3仅导致1个copy / move构造函数调用(在中foo10内部创建unordered_map),而Foo对的析构函数只有1个调用。(我稍后会再讲)。
对于foo11,我们直接将整数11传递给,emplace(11, d)以便在执行处于其方法内时unordered_map调用Foo(int)构造函数emplace()。与(2)和(3)不同,我们甚至不需要一些预先存在的foo对象来执行此操作。重要的是,请注意,仅对Foo构造函数进行了1次调用(创建了foo11)。
然后,我们直接将整数12传递给insert({12, d})。与with不同emplace(11, d)(该调用仅导致对Foo构造函数的1次调用),而对的调用insert({12, d})导致对Foo的构造函数两次调用(创建foo12和foo13)。
这表明insert()和之间的主要“全局”区别emplace()是:
使用insert() 几乎总是需要在范围内构造或存在某个Foo对象main()(随后是复制或移动),如果使用,emplace()则对Foo构造函数的任何调用都完全在内部unordered_map(即emplace()方法定义范围内)进行。传递给您的键的参数emplace()会直接转发到的定义中的Foo构造函数调用unordered_map::emplace()(可选的其他详细信息:其中,此新构造的对象会立即合并到unordered_map的成员变量中,因此在调用时不会调用析构函数执行离开emplace(),不会调用move或copy构造函数)。
注意:上面“ 几乎总是 ”中“ 几乎 ” 的原因在下面的I)中进行了说明。
- 续:之所以调用
umap.emplace(foo3, d)被称为Foo的非常量副本构造函数的原因如下:由于我们使用emplace(),所以编译器知道foo3(一个非常量Foo对象)是某些Foo构造函数的参数。在这种情况下,最合适的Foo构造函数是non-const copy构造函数Foo(Foo& f2)。这就是为什么没有umap.emplace(foo3, d)调用副本构造函数的原因umap.emplace(11, d)。
结语:
I.请注意,的一个重载insert()实际上等于 emplace()。如本cppreference.com页中所述,重载template<class P> std::pair<iterator, bool> insert(P&& value)(在cppreference.com页上的重载(2)insert())等效于emplace(std::forward<P>(value))。
二。然后去哪儿?
一个。试一下上面的源代码,并研究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}}));
您很快就会看到,std::pair构造函数(请参阅参考资料)最终被哪个重载使用,unordered_map可以对复制,移动,创建和/或销毁多少对象以及何时发生所有对象产生重要影响。
b。看看使用其他容器类(例如std::set或std::unordered_multiset)代替时会发生什么std::unordered_map。
C。现在,使用Goo对象(只是的重命名副本Foo),而不是int作为对象中的范围类型unordered_map(即,使用unordered_map<Foo, Goo>代替unordered_map<Foo, int>),并查看Goo调用了多少个构造函数。(剧透:有效果,但效果不是很大。)