emplace相关知识点总结
1 emplace与insert的异同
1.1 相同点
将容器中插入新成员
emplace是C++11新标准引入了新成员,同时引入的 还有emplace_front、emplace_back。分别对应容器的原有操作insert、push_front、push_back。
其功能分别为:将元素插入到一个指定的位置、将元素插入到容器头部、将元素插入到容器尾部。
1.2 不同点
是否创建临时对象,触发拷贝动作
- 调用push或者insert时,将元素类型的对象传递出去,这些对象被拷贝到容器当中,或者创建一个局部临时对象,并将其压入容器;
- 调用emplace时,则是将参数传递给元素类型的构造函数,emplace成员使用这些参数在容器管理的内存空间中直接构造元素,没有拷贝操作。
1.3 示例说明
1.3.1 源码
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//定义一个student类型
struct student {
student() = default;
student(const std::string &str, int num) : name_(str), num_(num) {
cout << name_ << " constructor called" << endl;
}
student(student &stu) {
this -> name_ = stu.name_;
this -> num_ = stu.num_;
cout << name_ << " copy constructor called" << endl;
}
student(student &&stu) {
this -> name_ = stu.name_;
this -> num_ = stu.num_;
cout << name_ << " move constructor called" << endl;
}
~student() {
cout << name_ << " destructor constructor called" << endl;
}
std::string name_;
int num_;
};
int main() {
vector<student> stu_vec;
stu_vec.reserve(10); // 不提前reserve会有多次拷贝操作
cout << "==========emplace_back right val==========" << endl;
stu_vec.emplace_back(student("lily", 3));
cout << stu_vec.size() << endl;//size = 1
stu_vec.emplace_back("bob", 1);//在stu_vec末尾构造一个student对象,使用2个参数的student构造函数
cout << stu_vec.size() << endl;//size = 2
cout << "==========push_back right val=========" << endl;
stu_vec.push_back(student("tom", 2));//正确,创建一个临时的student对象,传递给push_back
cout << stu_vec.size() << endl;//size = 3
// stu_vec.push_back("tom", 2);//错误,没有接受2个参数的push_back版本,push不支持直接构造
cout << "==========emplace_back left val==========" << endl;
student stu1("mike", 4);
stu_vec.emplace_back(stu1);
cout << stu_vec.size() << endl;//size = 4
cout << "==========push_back left val==========" << endl;
student stu2("jeck", 5);
stu_vec.emplace_back(stu2);
cout << stu_vec.size() << endl;//size = 5
return 0;
}
1.3.2 输出
image.png1.3.3结论
1)emplace和push的对象是右值
- emplace函数在容器中直接构造,push不支持直接构造, push则是先构造一个临时对象,再把该对象拷贝到容器中,临时对象还需要析构;
- 针对构造好的右值,emplace和push没有区别;
- 所有针对右值,emplace的效率更高。
2)emplace和push的对象是左值
- emplace是直接把构造好的左值对象拷贝到容器当中
- push也是直接把构造好的左值对象拷贝到容器当中
- 所以针对左值,emplace和push的效率是一样的
1.3.4 左值右值
在C++中,一个左值是指向一个指定内存的东西。另一方面,右值就是不指向任何地方的东西。通常来说,右值是暂时和短命的,而左值则活的很久,因为他们以变量的形式(variable)存在。我们可以将左值看作为容器(container)而将右值看做容器中的事物。
2 std::piecewise_construct_t
2.1 简述
c++11
struct piecewise_construct_t { };
c++14
struct piecewise_construct_t { explicit piecewise_construct_t() = default; };
- std::piecewise_construct_t 是一个空的struct标记类型,用于区分带有两个元组参数的不同函数, 是接收两个 tuple 参数的不同函数间消歧义的空类标签类型。。
- 不使用 std::piecewise_construct_t 的重载假定每个元组参数成为一对的元素。使用 std::piecewise_construct_t 的重载假定每个元组参数用于逐段构造指定类型的新对象,该对象将成为该pair的元素。
2.2 std::pair
- std::pair有一个特别的构造函数,第一个参数类型为std::piecewise_construct_t, 实际就是一个空结构体类型,用作标识。
- std::piecewise_construct_t作用就是其字面意思:分段构造。具体来说,因pair的key和value均可以是构造函数复杂类型,因而pair的初始化相对复杂,通过带有std::piecewise_construct_t类型参数,后跟两个tuple类型参数的构造函数,用第一个tuple类型的元素来构造pair的key, 用第二个tuple类型的元素来构造pair的value,从而实现pair的初始化。
- forward_as_tuple经常与std::piecewise_construct_t配合使用
pair构造函数
示例:
#include <iostream>
#include <utility>
#include <tuple>
struct Foo {
Foo(std::tuple<int, float>)
{
std::cout << "Constructed a Foo from a tuple\n";
}
Foo(int, float)
{
std::cout << "Constructed a Foo from an int and a float\n";
}
};
int main()
{
std::tuple<int, float> t(1, 3.14);
std::pair<Foo, Foo> p1(t, t);
std::pair<Foo, Foo> p2(std::piecewise_construct, t, t);
}
输出:
Constructed a Foo from a tuple
Constructed a Foo from a tuple
Constructed a Foo from an int and a float
Constructed a Foo from an int and a float
2.3 std::map
map 类型的 emplace处理比较特殊,因为和其他的容器不同,map 的 emplace 函数把它接收到的所有的参数都转发给 pair 的构造函数。对于一个 pair 来说,它既需要构造它的 key 又需要构造它的 value。如果我们按照普通的语法使用变参模板,我们无法区分哪些参数用来构造 key, 哪些用来构造 value。 比如下面的代码:
map<string, complex<double>> scp;
scp.emplace("hello", 1, 2); // 无法区分哪个参数用来构造 key 哪些用来构造 value
// string s("hello", 1), complex<double> cpx(2) ???
// string s("hello"), complex<double> cpx(1, 2) ???
所以我们需要一种方式既可以接受异构变长参数,又可以区分 key 和 value,解决 方式是使用 C++11 中提供的 tuple。
pair<string, complex<double>> scp(make_tuple("hello"), make_tuple(1, 2));
然后这种方式是有问题的,因为这里有歧义,第一个 tuple 会被当成是 key,第二 个tuple会被当成 value。最终的结果是类型不匹配而导致对象创建失败,为了解决 这个问题,C++11 设计了 piecewise_construct_t 这个类型用于解决这种歧义,它 是一个空类,存在的唯一目的就是解决这种歧义,全局变量 std::piecewise_construct 就是该类型的一个变量。所以最终的解决方式如下:
pair<string, complex<double>> scp(piecewise_construct, make_tuple("hello"), make_tuple(1, 2));
当然因为 map 的 emplace 把参数原样转发给 pair 的构造,所以你需要使用同样 的语法来完成 emplace 的调用,当然你可以使用 forward_as_tuple 替代 make_tuple,该函数会帮你构造一个 tuple 并转发给 pair 构造。
取舍
map<string, complex<double>> scp;
emplace方式:scp.emplace(piecewise_construct,forward_as_tuple("hello"),forward_as_tuple(1, 2));
insert方式:scp.insert({"world", {1, 2}});
- 对于emplace方式来说,虽然避免了临时变量的构造,但是却需要构建两个 tuple 。
- 从方便性和代码优雅性上来说,insert方式要胜过 emplace方式。
- 因此对于临时变量构建代价不是很大的对象(比如基础类型)推荐使用 insert 而不是 emplace。
参考:
https://www.jianshu.com/p/94b0221f64a5
https://blog.csdn.net/wangmj_hdu/article/details/119537411
https://blog.csdn.net/luoshabugui/article/details/118696418
https://www.cnblogs.com/guxuanqing/p/11396511.html