一,emplace_back和push_back
1,直接插入对象:emplace_back和push_back无区别
①当传递已经存在的对象时,是无区别的
#include <iostream>
#include <vector>
using namespace std;
/*
C++11 STL 容器 push/insert => emplace方法
*/
class Test
{
public:
Test(int a) {cout << "Test(int)" << endl;}
~Test() {cout << "~Test()" << endl;}
Test(int a, int b) {cout << "Test(int, int)" << endl;}
Test(const Test&) {cout << "Test(const Test&)" << endl;}
Test(Test &&) {cout << "Test(Test &&)" << endl;}
private:
};
int main()
{
Test t1(10);
vector<Test> v;
v.reserve(100);
cout << "---------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(t1);
v.emplace_back(t1);
cout << "---------" << endl;
return 0;
}
打印结果如下:
从以上打印结果可知。当直接插入已经存在对象时,emplace_back和push_back两个方法都是调用了Test的左值引用的拷贝构造函数,两者无区别。
②当传递的是临时对象时,也是没有区别:
#include <iostream>
#include <vector>
using namespace std;
/*
C++11 STL 容器 push/insert => emplace方法
*/
class Test
{
public:
Test(int a) {cout << "Test(int)" << endl;}
~Test() {cout << "~Test()" << endl;}
Test(int a, int b) {cout << "Test(int, int)" << endl;}
Test(const Test&) {cout << "Test(const Test&)" << endl;}
Test(Test &&) {cout << "Test(Test &&)" << endl;}
private:
};
int main()
{
vector<Test> v;
v.reserve(100);
cout << "---------" << endl;
// 直接插入对象,两个是没有区别的
v.push_back(Test(10));
v.emplace_back(Test(10));
cout << "---------" << endl;
return 0;
}
打印结果如下:
从以上打印结果可知。当直接插入临时对象时,emplace_back和push_back两个方法都是调用了Test的右值引用的拷贝构造函数,都做了资源转移,两者无区别。
2,当直接传递构造函数所需的实参时,emplace_back 比push_back更加高效
考虑如下代码:
#include <iostream>
#include <vector>
#include <map>
using namespace std;
class Test
{
public:
Test(int a) {cout << "Test(int)" << endl;}
~Test() {cout << "~Test()" << endl;}
Test(int a, int b) {cout << "Test(int, int)" << endl;}
Test(const Test&) {cout << "Test(const Test&)" << endl;}
Test(Test &&) {cout << "Test(Test &&)" << endl;}
private:
};
int main()
{
vector<Test> v;
v.reserve(100);
cout << "---------" << endl;
// 给 emplace传入Test对象构造所需要的参数,直接在容器底层构造对象即可
v.push_back(20);
cout << "---------" << endl;
v.emplace_back(20);
cout << "---------" << endl;
return 0;
}
打印结果:
从打印结果可知:如果是直接传递构造函数所需的参数下,push_back()首先是调用Test(int)创建了一个临时对象,然后调用右值引用拷贝构造传递给vector,再析构该临时对象。但是使用emplace_back(20),则是直接在容器底层构造对象即可。可见直接传递构造函数所需的实参时,emplace_back 比push_back更加高效。
同样的在其它容器中也一样,例如在map中:
map<int, string> m;
m.insert(make_pair(10, "zhang san"));
m.emplace(10, "zhang san"); //在map底层直接调用普通构造函数,生成一个pair对象
如果是调用map的insert构造一个临时的pair再去传递给map,效率很低。可以使用emplace,直接传递所需的实参,则会在map底层直接调用构造函数,生成pair对象,不会产生临时对象。
二,简单实现emplace_back
1,简易vector
template<typename T, typename Alloc = MyAlloccator<T>>
class vector
{
public:
vector() : vec_(nullptr), size_(0), idx_(0) {}
//预留内存空间
void reserve(size_t size)
{
vec_ = allocator_.allocate(size);
size_ = size;
}
//push_back
template<typename Type>
void push_back(Type&& val)
{
allocator_.construct(vec_ + idx_, std::forward<Type>(val));
}
// 1.引用折叠
template<typename... Types>
void emplace_back(Types&&... args)
{
//不管是左值引用,右值引用变量,它本身是左值。传递
//的过程中,要保持args的引用类型(左值? 右值?) 类型的完美转发
allocator_.construct(vec_ + idx_, std::forward<Types>(args)...);
idx_++;
}
private:
T* vec_;
int size_;
int idx_;
Alloc allocator_;
};
可以看出:emplace_back(Types&&... args)的形参使用的是可变参模板,为调用内存适配器allocator_的construct方法能够保持左值右值特性,使用forward保证此特性。
2,简易容器的空间适配器
//实现容器的空间配置器
template<typename T>
struct MyAlloccator
{
// allocate deallocate
// construct destory
T* allocate(size_t size)
{
return (T*)malloc(size * sizeof(T));
}
template<typename... Types>
void construct(T* ptr, Types&&... args)
{
//args只是一个参数,而且是Test对象,T也是一个Test类型
new (ptr) T(std::forward<Types>(args)...);
}
};
主要分析:void construct(T* ptr, Types&&... args),在ptr指针指向的空间上创建对象,为保证传递给创建对象的构造函数参数与传入construct的实参类型相同,使用forward完美转发。