前言
以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中。
你完全可以实现一个直接向硬件存取空间的allocator。
下面介绍的是SGI STL提供的配置器,配置的对象,是内存。(以下内容来自《STL源码剖析》)
空间配置器的标准接口
根据STL的规范,allocator的必要接口
- 各种typedef
1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4 allocator::reference 5 allocator::const_reference 6 allocator::size_type 7 allocator::difference_type 8 allocator::rebind // class rebind<U>拥有唯一成员other;是一个typedef,代表allocator<U>
- 默认构造函数和析构函数,因为没有数据成员,所以不需要初始化,但是必须被定义
1 allocator::allocator() 2 allocator::allocator(const allocator&) 3 template <class U> allocator::allocator(const allocator<U>&) 4 allocator::~allocator()
- 初始化,地址相关函数
1 // 配置空间,足以存储n个T对象,第二个参数是提示,能增进区域性 2 pointer allocator::allocate(size_type n, const void*=0) 3 4 size_type allocator::max_size() const 5 6 pointer allocator::address(reference x) const 7 const_pointer allocator::address(const_reference x) const
- 构建函数
1 void allocator::construct(pointer p, const T& x) 2 void allocator::destory(pointer p)
自己设计一个简单的空间配置器
1 #ifndef __VIGGO__ 2 #define __VIGGO__ 3 #include <new> // for placement new 4 #include <cstddef> // for ptrdiff_t, size_t 5 #include <cstdlib> // for exit() 6 #include <climits> // for UINT_MAX 7 #include <iostream> // for cerr 8 9 namespace VG { 10 11 template <class T> 12 inline T* _allocate(ptrdiff_t n, T*) { 13 set_new_handler(0); 14 T* tmp = (T*)(::operator new((size_t)(n * sizeof(T)))); 15 if (tmp == 0) { 16 cerr << "alloc memory error!" << endl; 17 exit(1); 18 } 19 return tmp; 20 } 21 22 template <class T> 23 inline void _deallocate(T* p) { 24 ::operator delete(p); 25 } 26 27 template <class T1, class T2> 28 inline void _construct(T1* p, const T2& value) { 29 new(p) T1(value); 30 } 31 32 template <class T> 33 inline void _destroy(T* p) { 34 p->~T(); 35 } 36 37 template <class T> 38 class allocator { 39 public: 40 typedef T value_type; 41 typedef T* pointer; 42 typedef const T* const_pointer; 43 typedef T& reference; 44 typedef const T& const_reference; 45 typedef size_t size_type; 46 typedef ptrdiff_t difference_type; 47 48 template <class U> 49 struct rebind { 50 typedef allocator<U> other; 51 }; 52 53 pointer address(reference x) {return (pointer)&x;} 54 const_pointer address(const_reference x) const { 55 return (const_pointer)&x; 56 } 57 58 pointer allocate(size_type n, const void *hint=0) { 59 return _allocate((difference_type)n, (pointer)0); // mark 60 } 61 62 void deallocate(pointer p, size_type n) { 63 _deallocate(p); 64 } 65 66 size_type max_size() const {return size_type(UINT_MAX / sizeof(T));} 67 68 void construct(pointer p, const T& x) { 69 _construct(p, x); 70 } 71 72 void destroy(pointer p) { 73 _destroy(p); 74 } 75 }; 76 } 77 #endif
放在 vector<int, VG::allocator<int> > 中测试,可以实现简单的内存分配,但是实际上的 allocator 要比这个复杂。
SGI特殊的空间配置器
标准的allocator只是基层内存配置/释放行为(::operator new 和 ::operator delete)的一层薄薄的包装,并没有任何效率上的强化。
现在我们看看C++内存配置和释放是怎样做的:
new运算分两阶段(1)调用 ::operator new 配置内存;(2) 调用对象构造函数构造对象内容。
delete运算也分两阶段(1) 调用对象的析构函数;(2)调用 ::operator delete 释放内存。
为了精密分工,STL allocator决定将两阶段操作区分开来,内存配置由 alloc::allocate() 负责。内存释放操作由 alloc::deallocate()负责;对象构造由 ::construct() 负责,对象析构由 ::destroy() 负责。
构造和析构基本工具:construct() 和 destroy()
construct() 接受一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上。C++的placement new运算子可用来完成这一任务。
destory()有两个版本,一是接受一个指针,直接调用该对象的析构函数即可。另外一个接受first和last,将半开范围内的所有对象析构。首先我们不知道范围有多大,万一很大,而每个对象的析构函数都无关痛痒(所谓 trivial destructor),那么一次次调用这些无关痛痒的析构函数是一种浪费。所以我们首先判断迭代器所指对象是否为 trivial(无意义), 是则什么都不用做;否则一个个调用析构。
上图为construct的实现函数
上图为destroy的实现函数
这里用到我们神奇的 __type_traits<T>,之前介绍的 traits 是 萃取返回值类型 和 作为重载依据的,现在为每一个内置类型特化声明一些tag。
现在我们需要用到 真 和 假 两个标志:
示例:
空间的配置和释放:std::alloc
SGI的设计哲学: 1. 向 system heap 要求空间; 2. 考虑多线程状态(先略过);3. 考虑内存不足时的应变措施;4. 考虑过多“小型区块”可能造成的内存碎片问题。
SGI设计了双层级配置器,第一级配置器直接使用 malloc() 和 free(),第二级配置器则视情况采用不同的策略;当配置区块超过128bytes时,交给第一级配置器。
整个设计究竟只开放第一级配置器,或是同时开放第二级配置,取决于__USE_MALLOC时候被定义:
1 # ifdef __USE_MALLOC 2 ... 3 typedef __malloc_alloc_template<0> malloc_alloc; 4 typedef malloc_alloc alloc; // 令alloc为第一级配置器 5 #else 6 ... 7 // 令alloc为第二级配置器 8 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>alloc; 9 #endif
其中__malloc_alloc_template就是第一级配置器,__default_alloc_template为第二级配置器。alloc并不接受任何template型别参数。
无论alloc被定义为第一级或第二级配置器,SGI还为它在包装一个接口如下,使配置器的接口能够符合STL规格:
1 template <class T, class Alloc> 2 class simple_alloc { 3 public: 4 static T *allocate(size_t n) 5 {return 0==n? 0 : (T*)Alloc::allocate(n * sizeof(T));} 6 static T *allocate(void) 7 {return (T*)Alloc::allocate(sizeof(T));} 8 static void deallocate(T *p, size_t n) 9 {if (0 != n) Alloc::deallocate(p, n*sizeof(T));} 10 static void deallocate(T *p) 11 {Alloc::deallocate(p, sizeof(T));}
一二级配置器的关系,接口包装,及实际运用方式,
第一级配置器 __malloc_alloc_template
1 #if 0 2 # include <new> 3 # define __THROW_BAD_ALLOC throw bad_alloc 4 #elif !defined(__THROW_BAD_ALLOC) 5 # include <iostream> 6 # define __THROW_BAD_ALLOC cerr << "out of memery" << endl; exit(1); 7 #endif 8 9 // malloc-based allocator.通常比稍后介绍的 default alloc 速度慢 10 // 一般而言是thread-safe,并且对于空间的运用比较高效 11 // 以下是第一级配置器 12 // 注意,无“template型别参数”。置于“非型别参数”inst,则完全没排上用场 13 template <int inst> 14 class __malloc_alloc_template { 15 private: 16 //以下都是函数指针,所代表的函数将用来处理内存不足的情况 17 static void *oom_malloc(size_t); 18 static void *oom_realloc(void*, size_t); 19 static void (* __malloc_alloc_oom_handler)(); 20 public: 21 static void * allocate(size_t n) { 22 void *result = malloc(n); // 第一级配置器直接使用malloc 23 // 无法满足需求时,改用oom_malloc 24 if (0 == result) result = oom_malloc(n); 25 return result; 26 } 27 28 static void deallocate(void *p, size_t /* n */) { 29 free(p); // 第一级配置器直接用free() 30 } 31 32 static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz) { 33 void *result = realloc(p, new_sz); 34 if (0 == result) result = oom_realloc(p, new_sz); 35 return result; 36 } 37 38 // 以下仿真C++的 set_handler()。换句话,你可以通过它 39 // 指定自己的 out-of-memory handler,企图释放内存 40 // 因为没有调用 new,所以不能用 set_new_handler 41 static void (* set_malloc_handler(void (*f)())) () { 42 void (*old)() = __malloc_alloc_oom_handler; 43 __malloc_alloc_oom_handler = f; 44 return old; 45 } 46 }; 47 48 // 初值为0,待定 49 template <int inst> 50 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; 51 52 template <int inst> 53 void * __malloc_alloc_template<inst>::oom_malloc(size_t n) { 54 void (* my_malloc_handler)(); 55 void *result; 56 57 for (;;) { 58 my_malloc_handler = __malloc_alloc_oom_handler; 59 if (0 = my_malloc_handler) {__THROW_BAD_ALLOC;} // 如果没设置 60 (* my_malloc_handler)(); // 调用处理例程,企图释放内存 61 result = malloc(n); // 再次尝试配置内存 62 if (result) return result; 63 } 64 } 65 66 template <int inst> 67 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) { 68 void (* my_malloc_handler)(); 69 void *result; 70 71 for (;;) { 72 my_malloc_handler = __malloc_alloc_oom_handler; 73 if (0 == my_malloc_handler) {__THROW_BAD_ALLOC;} 74 (*my_malloc_handler)(); 75 result = realloc(p, n); 76 if (result) return result; 77 } 78 }