空间配置器
“new”的实现
这里直接我们直接来看STL的construct实现吧
1 2 3 4 5 6
| template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p) T1(value); }
|
可以明白这里就只是一个placement new的调用, 只是用了泛型来实现一个对象分配的模板, 并实现初始化.
既然已经看到了对象的分配, 那再接再厉看看空间的分配, 充分了解STL是怎么将new分开执行的. allocate函数实现空间的申请, 但是这里有一点看不出来, 申请内存是有分为一级配置器和二级配置器, 分配的空间小于128字节的就调用二级配置器, 大于就直接使用一级配置器, 一级配置器直接调用malloc申请, 二级使用内存池.
1 2 3 4 5 6 7 8 9
| template <class T> inline T *allocate(ptrdiff_t size, T *) { std::set_new_handler(0); T *tmp = (T *)(::operator new((size_t)(size * sizeof(T)))); if (!tmp) { std::cerr << "out of memort" << std::endl; exit(1); } return tmp; }
|
new_handler,顾名思义就是一个处理程序,当程序向内存的分配请求无法满足时将有两种可能:
- 抛出异常
- 设置一个异常处理函数,这就是所谓的new_handler(类似于中断机制,本质上来说就是一个函数指针)
当第二种情况发生以后,我们可以通过new_handler删除无用的内存,以及设置新的new_handler,而这个set_new_handler就是来进行设置的。
set_new_handler(0)主要是为了卸载目前的内存分配异常处理函数,这样一来一旦分配内存失败的话,C++就会强制性抛出std:bad_alloc异常,而不是跑到处理某个异常处理函数去处理。
::访问符放到最前面的意思是使用全局版本
new一共有三兄弟:
new operator (就是我们常用的new)
operator new
placement new
先说说这个new,我们在程序中使用new的时候,实际上做了两件事情:申请内存和构造对象
简单的理解,new完成了一套比较完备的服务,而operator new,只是申请内存,placement new是在申请的内存中进行构造对象,第2、3中形式就是对new的拆分。
1 2 3 4 5
| template<class T1, class T2> inline void _construct(T1* p, const T2& value) { new(p)T1(value); }
|
这里p指向一块内存,我们直接在这个内存上构造对象(调用T1的构造函数),即
内存分配果然是调用operator new来执行空间分配, 这里allocate和construct都只是简单的对operator new进行封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const int N = 4; int main() { std::allocator<std::string> alloc; auto str_ve = alloc.allocate(N); auto p = str_ve; alloc.construct(p++); alloc.construct(p++, 10, 'a'); alloc.construct(p++, "construct"); std::cout << str_ve[0] << std::endl; std::cout << str_ve[1] << std::endl; std::cout << str_ve[2] << std::endl;
while (p != str_ve) { alloc.destroy(--p); } alloc.deallocate(str_ve, N);
exit(0); }
|
运行结果:
这个程序首先调用allocate申请N个大小的空间, 在依次construct调用构造函数, 这里就先初始化3个结构, 紧接着通过destory调用析构函数, 最后deallocate释放申请的空间. 整个过程很容易理解, 但是这里还要深入是dealllocate和destroy两个函数.
“delete”实现
先是看destroy调用析构函数. 而destroy有两个版本.
版本一:
需要传入的参数 : 一个指针
1 2 3 4 5
| template <class T> inline void destroy(T* pointer) { pointer->~T(); }
|
版本一直接就调用了析构函数, 不用过多的分析.
版本二:
需要传入的参数 : 两个迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template <class ForwardIterator> inline void destroy(ForwardIterator first, ForwardIterator last) { __destroy(first, last, value_type(first)); }
template <class ForwardIterator, class T> inline void __destroy(ForwardIterator first, ForwardIterator last, T*) { typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor; __destroy_aux(first, last, trivial_destructor()); }
|
destroy直接调用__destroy, 前者只是一个接口, 所以重点是在后者.
分析__type_traits<> : 它是用于获取迭代器所指对象的类型,运用traits技法实现的.只要记住我们用来获取对对象类型就可以了. 然后通过类型的不一样选择执行不同的析构调用.
第二版本接受first和last两个迭代器,将两个迭代器范围内的对象析构掉。在第二版本中运用了traits编程技法,traits会得到当前对象的一些特性,再根据特性的不同分别对不同特性的对象调用相应的方法。在第二版本中,STL会分析迭代器所指对象的has_trivial_destructor特性的类型(只有两种:_true_type和__false_type),如果是__true_type,STL就什么第一不做;如果是false_type,就会调用每个对象的析构函数来销毁这组对象。
当__type_traits为__false_type时, 调用的是下面这个函数, 通过迭代所有的对象并调用版本一的函数执行析构函数进行析构. 而这个是被称为non-travial destructor
1 2 3 4 5 6 7
| template <class ForwardIterator> inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) { for ( ; first < last; ++first) destroy(&*first); }
|
当__type_traits为__true_type时, 什么也不做, 因为这样效率很高效, 并不需要执行析构函数. 而这个是被称为travial destructor.
1 2 3
| template <class ForwardIterator> inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}
|
最后是版本二的特化版, 同样也什么都不用做, 没有必要做析构.
1 2
| inline void destroy(char*, char*) {} inline void destroy(wchar_t*, wchar_t*) {}
|
destroy分为这么几个版本和几个不同的函数执行都是为了提升效率, 较小的调用并不能看出什么, 但是如果是范围析构的话这样不同的选择析构能很节约时间和效率.
完整实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| #ifndef _JJALLOC_H_ #define _JJALLOC_H_
#include <climits> #include <iostream>
namespace JJ {
template <class T> inline T *_allocte(ptrdiff_t size, T *) { std::set_new_handler(0); T *tmp = (T *)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { std::cerr << "out of memory" << std::endl; exit(1); } return tmp; }
template <class T> inline void _deallocate(T *buffer) { ::operator delete(buffer); }
template <class T1, class T2> inline void _construct(T1 *p, const T2 &value) { new (p) T1(value); }
template <class T> inline void _destroy(T *ptr) { ptr->~T(); }
template <class T> class allocator { public: typedef T value_type; typedef T *pointer; typedef const T *const_pointer; typedef T &reference; typedef const T &const_reference; typedef size_t sizt_type; typedef ptrdiff_t difference_type;
template <class U> struct rebind { typedef allocator<U> other; };
pointer allocate(sizt_type n, const void *hint = 0) { return _allocte((difference_type)n, (pointer)0); }
void deallocate(pointer p, sizt_type n) { _deallocate(p); }
void construct(pointer p, sizt_type n) { _destroy(p); }
void destroy(pointer p) { _destroy(p); }
const_pointer const_address(const_reference x) { return (const_pointer)&x; }
sizt_type max_size() const { return sizt_type(UINT_MAX / sizeof(T)); } };
} #endif
|