深度剖析空间配置器(一)构造和析构函数

来源:互联网 发布:北京知果科技 编辑:程序博客网 时间:2024/06/05 17:39

STL空间配置器

STL为什么需要空间适配器

原因1、频繁的申请和释放空间,会降低运行的效率

频繁的申请和释放,就会频繁的调用malloc函数和free函数

 调用函数会压栈出栈,这些都是有开销的

原因2、频繁的申请和释放空间,会造成内存碎片的问题


这就会造成,即使是有足够的内存(40字节),但是通过申请并不能得到一个连续24字节的内存

从而引入了空间配置器

空间适配器

概念

空间适配器是STL,标准模板库的六大组件之一,其余分别是:容器,迭代器,算法,配接器,仿函数



主要分三个文件实现,首先我们来介绍第一个文件stl_construct.h  这里定义了全局函数construct()和destroy(),负责对象的构造和析构。


先介绍构造函数 construct():

#include <new.h>  //定位new 的头文件template <class _T1, class _T2>inline void _Construct(_T1* __p, const _T2& __value) {  new ((void*) __p) _T1(__value);  // placement new; 调用 _T1(__value)构造新对象到预分配的内存p上}template <class _T1>inline void _Construct(_T1* __p) {  new ((void*) __p) _T1();  //无参函数构造新对象}

template <class _T1, class _T2>inline void construct(_T1* __p, const _T2& __value) {  _Construct(__p, __value);}template <class _T1>inline void construct(_T1* __p) {  _Construct(__p);}



placement new 允许你在一个已经分配好的内存中(栈或堆)构造一个新的对象,原型中 (void*) __p 实际上就是指向一个已经分配好的内存缓冲区的首地址。STL 借助C++中的 placement new 来提高效率,因为使用 new 操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常。借助 placement new 就可以解决这个问题,我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数,而且不会出现在程序运行中途出现内存不足的异常。


析构函数destroy():

//单个对象template <class _Tp>inline void _Destroy(_Tp* __pointer) {  __pointer->~_Tp();}//迭代器版本template <class _ForwardIterator>void__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)//对象的析构函数不是无意义的,函数内部调用destroy()  {  for ( ; __first != __last; ++__first)    destroy(&*__first);  //调用destroy析构一个个对象}template <class _ForwardIterator> inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {} //对象的析构函数是无意义的,什么都不做。//这个函数判断对象的析构函数是否是 无意义的。template <class _ForwardIterator, class _Tp>inline void __destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*){  typedef typename __type_traits<_Tp>::has_trivial_destructor          _Trivial_destructor;   //判断析构函数是否 是无意义的。  __destroy_aux(__first, __last, _Trivial_destructor());}//这个函数判断迭代器所指向的对象的类型的指针。template <class _ForwardIterator>inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {  __destroy(__first, __last, __VALUE_TYPE(__first)); //__VALUE_TYPE(__first)  迭代器所指向对象的类型的指针。}//迭代器版本  destroy()_Destroy() -> __destroy() -> __destroy_aux() -> destroy() 指针版本  //所以实际上是逐个析构,只是中间为了效率,引入了判断元素的指针型别,来确定对象是否具备有用的析构函数 
template <class _Tp>inline void destroy(_Tp* __pointer) {  _Destroy(__pointer);}template <class _ForwardIterator>inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {  _Destroy(__first, __last);}

上面可以看出,对于迭代器版本的destroy() 实际最后都落脚到了单一对象指针版本的 destroy()。那么为什么要绕一大圈呢?因为这里接受的参数是两个迭代器,目的就是将这两个迭代器范围内的所有对象都析构掉,如果范围很大,并且对象的析构函数都是无关紧要的,那么一次次的调用这些个无用的析构函数,势必会对效率产生大的影响,所以先利用__VALUE_TYPE(__first) 获得迭代器所指对象的型别的指针,再利用 __type_traits<_Tp> 判断该型别的析构函数是否是无意义的,如果是则什么也不做,如果不是则循环逐个析构范围内的对象。


所以对象的构造实际是通过 placement new 完成的(缓存提前分配然后进行对象的分配);对象的析构则通过调用外在对象的析构函数(~_Tp())。注意的是,这里的析构只是析构对象,分配好的缓存并没有释放,所以可以反复利用缓存并给它分配对象。不打算再次使用这个缓存时,你可以delete 将其释放掉。


另外STL(v3.3)版本destroy() 还针对迭代器类型为定义了特化版本

inline void _Destroy(char*, char*) {}inline void _Destroy(int*, int*) {}inline void _Destroy(long*, long*) {}inline void _Destroy(float*, float*) {}inline void _Destroy(double*, double*) {}inline void _Destroy(wchar_t*, wchar_t*) {}



0 0
原创粉丝点击