深度剖析空间配置器(一)构造和析构函数
2017-04-19 11:38
239 查看
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*) {}
相关文章推荐
- string 构造 拷贝 和析构函数
- 不要在构造和析构函数中调用虚函数
- C++ 构造函数、析构函数、拷贝构造、赋值运算符
- C++构造/析构函数Q&A
- C++箴言:避免构造或析构函数中调用虚函数
- 构造/析构函数
- 类的构造函数、析构函数与赋值函数(三)构造和析构次序
- c++/string的构造析构函数
- 继承中的构造和析构函数
- c++访问权限之构造和析构函数说明
- 构造方法,析构函数
- 子类继承和调用父类的构造、析构函数方法
- C++ 将对象的构造和析构函数声明为Protected的作用
- C++ 将对象的构造和析构函数声明为Protected的作用
- 3.2默认的无参构造和析构函数
- 绝不在构造和析构函数中调用 virtual 函数
- c++构造与析构函数
- C++类的构造、拷贝构造、析构函数等
- C++之构造函数,拷贝构造,析构函数
- 由一道面试题引发的“血案”(静态变量,虚函数,构造/析构函数调用顺序等)