和前面学的 map、set 等容器一样,C++ 11 标准也为 unordered_map 容器新增了 emplace() 和 emplace_hint() 成员方法,本节将对它们的用法做详细的介绍。
我们知道,实现向已有 unordered_map 容器中添加新键值对,可以通过调用 insert() 方法,但其实还有更好的方法,即使用 emplace() 或者 emplace_hint() 方法,它们完成“向容器中添加新键值对”的效率,要比 insert() 方法高。
至于为什么 emplace()、emplace_hint() 执行效率会比 insert() 方法高,可阅读《为什么emplace()、emplace_hint()执行效率比insert()高》一文,虽然此文的讲解对象为 map 容器,但就这 3 个方法来说,unordered_map 容器和 map 容器是一样的。
emplace() 方法的用法很简单,其语法格式如下:
template
pair emplace ( Args&&... args );
其中,参数 args 表示可直接向该方法传递创建新键值对所需要的 2 个元素的值,其中第一个元素将作为键值对的键,另一个作为键值对的值。也就是说,该方法无需我们手动创建键值对,其内部会自行完成此工作。
emplace(key,value)
另外需要注意的是,该方法的返回值为 pair 类型值,其包含一个迭代器和一个 bool 类型值:
#include
#include
#include
int main() {
//创建unorder_map容器
std::unordered_map u_map;
u_map.emplace("博客", "https://blog.csdn.net/qq_44918090?spm=1010.2135.3001.5343");
for (auto iter = u_map.begin(); iter != u_map.end(); ++iter) {
std::cout << (*iter).first << " " << (*iter).second << std::endl;
}
std::cout << std::endl;
std::pair::iterator, bool> ret = u_map.emplace("百度","www.baidu.com");
std::cout << ret.second << std::endl;
std::cout << ret.first->first << " " << (*(ret.first)).second;
return 0;
}
输出结果:
博客 https://blog.csdn.net/qq_44918090?spm=1010.2135.3001.5343
1
百度 www.baidu.com请按任意键继续. . .
C++11在性能上做了很大的改进,最大程度的减少了内存移动和拷贝,除了前面说的右值引用外,还有下面两个:
在C++11之前,向vector中插入数据时常用的方法是push_back,从C++11开始,又提供了empalce,emplace_back方法,这些方法可以看成是push_back的替代品,不但使用简单,而且性能提升也比较明显。emplace_back的使用方法如下:
void Func2() {
struct A {
int a_;
double b_;
A(int a,double b)
:a_(a),
b_(b){
std::cout << "emplace_back" << std::endl;
}
};
std::vector vec;
vec.emplace_back(1, 2.0);
A a(2, 3.0);
vec.push_back(a);
for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
std::cout << (*iter).a_ << " " << (*iter).b_ << std::endl;
}
}
从上面的代码可以看出,emplace_back方法使用简单,可以直接通过构造函数构造临时对象,因此,在实际编码的时候,我们也需要提供对象的构造方法,如果不提供,编译时将会报错,可以注释掉构造函数验证下。
emplace_back
emplace_back
1 2
2 3
请按任意键继续. . .
相比push_back,emplace_back的性能优势也很明显,emplace_back通过减少内存移动和拷贝从而提升容器的插入性能,可以在上面的代码基础上改造完成。
void Func3() {
struct A
{
int x;
double y;
std::string z;
//构造
A(int a, double b, std::string c)
:x(a)
, y(b)
, z(c) {
std::cout << "is constructed" << std::endl;
}
//
A(const A& otherA)
:x(otherA.x)
, y(otherA.y)
, z(std::move(otherA.z)) {
std::cout << "is moved" << std::endl;
}
};
std::vector v;
std::cout << "------emplace_back:---------" << std::endl;
v.emplace_back(1, 2, "helloword");
std::cout << "------push_back:---------" << std::endl;
v.push_back(A(3, 4, "china"));
}
运行结果:
------emplace_back:---------
is constructed
------push_back:---------
is constructed
is moved
is moved
从结果可以看出,在对vector的插入过程中,push_back方法构造了一次,移动了两次;使用emplace_back只进行了一次构造,没有进行内存的移动。
综上可以看出,在实际的应用中应该使用emplace系列函数代替传统的push_back等相关函数,但也需要注意一点,如果类或者结构体中没有提供构造函数,那么就不能使用emplace系列函数进行替换。
C++11中新增了无序容器,如:unordered_map/unordered_multimap和unordered_set/unordered_multiset容器,在实际插入时,这些容器不在进行排序,因此相对有序的map和set来说效率都有提升。
map和set的底层实现是红黑树,对应的无序容器底层实现是Hash Table,由于内部通过哈希进行快速操作因此效率将会更高。在使用无序容器时,如果是基本类型数据,则不需要提供哈希函数和比较函数,使用方法和普通的map、set是一样的,如果数据类型是自定义的,在使用时需要提供哈希函数和比较函数,具体代码如下:
emplace是C++11新标准引入了新成员,同时引入的 还有emplace_front、emplace_back。分别对应容器的原有操作insert、push_front、push_back。
其功能分别为:将元素插入到一个指定的位置、将元素插入到容器头部、将元素插入到容器尾部。
**调用push或者insert时,将元素类型的对象传递出去,这些对象被拷贝到容器当中,或者创建一个局部临时对象,并将其压入容器。**使用 insert() 向 map 容器中插入键值对的过程是,先创建该键值对,然后再将该键值对复制或者移动到 map 容器中的指定位置。
调用emplace时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素,没有拷贝操作。使用 emplace() 或 emplace_hint() 插入键值对的过程是,直接在 map 容器中的指定位置构造该键值对。
#include
#include
程序输出结果为:
insert():
调用构造函数
调用移动构造函数
调用移动构造函数
emplace():
调用构造函数
emplace_hint():
调用构造函数
在大部分情况下,emplace函数可以在集合内直接创建新元素,而不需要将现有元素复制或移动到集合内,使用emplace函数能够减少复制或移动构造函数的开销,能提供比insert、push等函数更高的性能。
但对于std::map和std::unordered_map而言,在某些情况下insert可能比emplace更快。
例如:
调用std::map
::insert函数需要传入一个std::pair 对象。在实际插入时,这个pair会用于复制构造或移动构造map中实际的存储对象,这样会产生一次复制操作。 调用std::map
::emplace函数时,则会使用传入的参数直接在实际的存储位置原地构造一个std::pair ,这样通常可以减少一次复制操作 但是,若key原本就已经存在,则insert只需完成键的对比就可以直接返回了,而emplace将必须原地构造一个新的对象才能开始对比,使用emplace将需要额外的构造开销。
大部分情况下,insert的效率不如emplace,但若key已存在情况下,insert效率优于emplace。