C++11容器的push和insert方法,都匹配了emplace的新的方法,也是用来添加数据的。
emplace使用的时候感觉和之前的功能是一样的,都是添加新的元素,但是到底有什么不同呢?
#include#include using namespace std; class Test { public: Test(int a) { cout << "Test(int)"< v; v.reserve(100);//只开辟空间,没有构建对象 cout << "=====================" << endl; v.push_back(t1);//匹配的是带左值引用参数的拷贝构造函数 v.emplace_back(t1);//匹配的是带左值引用参数的拷贝构造函数 cout << "=====================" << endl; return 0; }
如果是直接插入 已存在的 是左值的 对象,两个方法是没有区别的,都是调用带左值引用参数的拷贝构造函数构建vector上的对象
如果插入的是临时对象呢?
#include#include using namespace std; class Test { public: Test(int a) { cout << "Test(int)"< v; v.reserve(100);//只开辟空间,没有构建对象 cout << "=====================" << endl; v.push_back(Test(20));//匹配的是带右值引用参数的拷贝构造函数 v.emplace_back(Test(20));//匹配的是带右值引用参数的拷贝构造函数 cout << "=====================" << endl; return 0; }
临时对象的生命周期很短,只存在当前语句中。
都是生成临时对象,然后调用带右值引用参数的拷贝构造函数构建vector上的对象
这两种方法还是没有区别啊!
只要是插入对象,这2种方法是没有区别的!!!
示例代码3我们现在传入的是整数参数
#include#include using namespace std; class Test { public: Test(int a) { cout << "Test(int)"< v; v.reserve(100);//只开辟空间,没有构建对象 cout << "=====================" << endl; v.emplace_back(20); v.emplace_back(20, 40); cout << "=====================" << endl; return 0; }
现在相当于是实参在传递的过程中,没有定义过对象,没有生成过临时对象!!!
直接传入你要构建Test对象所需要的参数(构造函数的参数),然后直接在vector底层调用了相应的构造函数—在vector底层直接构造Test对象了。
这样做,当然好,效率直接就提升了啊!而且写起来特别简单,不需要像push_back必须要传Test对象参数:
int main()
{
unordered_mapmap;
map.insert(make_pair(10, "linzeyu"));
//构建临时对象,然后拿临时对象调用右值引用参数的拷贝构造函数构造map上的对象
//在map底层构建好对象后,出了这条语句,临时对象析构。
map.emplace(10, "zhang san");
//直接传入构建对象所需要的参数,在map底层直接调用普通构造函数生成对象了,
//没有产生临时对象等额外的对象,没有额外的函数的调用
return 0;
}
我们看看emplace的实现原理
emplace是通过可变参模板参数来实现的。我们可以理解成C语言的可变参函数:printf
#includeusing namespace std; //定义容器的空间配置器,和C++标准库的allocator实现一样 template struct MyAllocator//模板类 struct定义 默认公有的 { T* allocate(size_t size)//负责内存开辟 { return (T*)malloc(sizeof(T) * size); } void deallocate(void* p)//负责内存释放 { free(p); } template void construct(T* p, Types&& ...args) { //如果args是个Test对象,T是Test类型,调用的是Test的拷贝构造函数了 //如果args是构建对象的参数,匹配的也就是相应的构造函数了 new (p) T(std::forward (args)...); } void destroy(T* p)//负责对象析构 { p->~T();// ~T()代表了T类型的析构函数 } }; template >//初始化,让用户不用指定,用默认的 class Vector { public: Vector(int size = 10)//构造函数 { //需要把内存开辟和对象构造分开处理 //_first = new T[size]; _first = _allocator.allocate(size); _last = _first; _end = _first + size; } ~Vector()//析构函数 { //析构容器有效的元素,然后释放_first指针指向的堆内存 //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作 } _allocator.deallocate(_first);//释放堆上的数组内存 _first = _last = _end = nullptr; } Vector(const Vector & rhs)//拷贝构造函数 { int size = rhs._end - rhs._first;//空间大小 //_first = new T[size]; _first = _allocator.allocate(size); int len = rhs._last - rhs._first;//有效数据的长度 for (int i = 0; i < len; ++i) { //_first[i] = rhs._first[i]; _allocator.construct(_first + i, rhs._first[i]); } _last = _first + len; _end = _first + size; } Vector & operator=(const Vector & rhs)//赋值函数 { if (this == &rhs) return *this; //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作 } _allocator.deallocate(_first); int size = rhs._end - rhs._first; //_first = new T[size]; _first = _allocator.allocate(size); int len = rhs._last - rhs._first; for (int i = 0; i < len; ++i) { //_first[i] = rhs._first[i]; _allocator.construct(_first + i, rhs._first[i]); } _last = _first + len; _end = _first + size; return *this; } template //函数模板的类型推演 + 引用折叠 void push_back(Ty&& val)//Ty CMyString& + && = CMyString& { if (full()) expand(); //move(左值):移动语义,得到右值类型 (int&&)a //forward:类型完美转发,能够识别左值和右值类型 _allocator.construct(_last, std::forward (val)); _last++; } void pop_back()//从容器末尾删除元素 { if (empty()) return; //--_last; //不仅要把_last指针--,还需要析构删除的元素 --_last; _allocator.destroy(_last); } T back()const//返回容器末尾的元素的值 { return *(_last - 1); } bool full()const { return _last == _end; } bool empty()const { return _first == _last; } int size()const { return _last - _first; } //引用折叠,这个概念在我的《理解C++的右值引用并进行应用(CMyStirng)》博客中有解释 //可接收实参的左值或者右值,动态可变 template //可变参模板参数 void emplace_back(Types&&...args)//右值引用,而且函数可以接受多个参数,不固定的任意的 //我们可以传很多个参数,但是它底层看Test构造函数需要几个参数,如果传入的参数超过了,就报错 { _allocator.construct(_last, std::forward (args)...); _last++; } private: T* _first;//指向数组起始的位置 T* _last; //指向数组中有效元素的后继位置 T* _end;//指向数组空间的后继位置 Alloc _allocator;//定义容器的空间配置器对象 void expand()//容器的二倍扩容 { int size = _end - _first; //T *ptmp = new T[2 * size]; T* ptmp = _allocator.allocate(2 * size); for (int i = 0; i < size; ++i) { //ptmp[i] = _first[i]; _allocator.construct(ptmp + i, _first[i]); } //delete[]_first; for (T* p = _first; p != _last; ++p) { _allocator.destroy(p); } _allocator.deallocate(_first); _first = ptmp; _last = _first + size; _end = _first + 2 * size; } }; class Test { public: Test(int a) { cout << "Test(int)" << endl; } Test(int a, int b) { cout << "Test(int,int)" << endl; } Test(const Test& a) { cout << "Test(const Test&)" << endl; } Test(const Test&& a) { cout << "Test(const Test&&)" << endl; } ~Test() { cout << "~Test()" << endl; } }; int main() { Vector v; cout << "=====================" << endl; v.emplace_back(20); v.emplace_back(20, 40); cout << "=====================" << endl; return 0; }
非常的OK!!!
我摘出emplace的实现核心代码如下,方便大家观看:
//定义容器的空间配置器,和C++标准库的allocator实现一样 templatestruct MyAllocator//模板类 struct定义 默认公有的 { T* allocate(size_t size)//负责内存开辟 { return (T*)malloc(sizeof(T) * size); } void deallocate(void* p)//负责内存释放 { free(p); } template void construct(T* p, Types&& ...args) { //如果args是个Test对象,T是Test类型,调用的是Test的拷贝构造函数了 //如果args是构建对象的参数,匹配的也就是相应的构造函数了 new (p) T(std::forward (args)...); } void destroy(T* p)//负责对象析构 { p->~T();// ~T()代表了T类型的析构函数 } }; //引用折叠,这个概念在我的《理解C++的右值引用并进行应用(CMyStirng)》博客中有解释 //可接收实参的左值或者右值,动态可变 template //可变参模板参数 void emplace_back(Types&&...args)//右值引用,而且函数可以接受多个参数,不固定的任意的 //我们可以传很多个参数,但是它底层看Test构造函数需要几个参数,如果传入的参数超过了,就报错 { _allocator.construct(_last, std::forward (args)...); _last++; }



