STL容器可以保存任何C++语言支持的基本类型、用户自定义类型的对象。容器本身只是实现了一种数据结构,对用户数据进行管理。用户数据最终都是保存在内存中的,那么内存的申请、释放工作是由“空间配置器”承担的。标准c++中都提供了std::allocator类。
当容器中保存的是用户自定义类型数据时,有的数据类型结构简单,占用的空间小;而有的数据类型结构复杂,需要的内存空间大。有的应用程序,需要频繁的进行数据元素的插入、删除操作,这就对应这频繁的内存空间的申请、释放工作。大家都知道频繁的内存操作,会产生严重的性能问题。为了解决这些问题,stlport提供了两个空间配置器。一个是“简单空间配置器”,只是对c运行时库中malloc/free函数进行了简单的封装,唯一不同的是,提供类似new异常处理机制。另一个是“基于内存池的空间配置器”,容器每次申请内存的时候,空间配置器会基于一定的策略,向os申请交大的内存,避免每次内存申请都向os申请。
下面用图片说明STL具有次级空间配置能力。根据上图说明,我们可以写出很简陋地STL空间配置器代码:
/* __malloc_alloc_template.h文件 ** 用来模拟STL中的第一级空间配置器,充分体现了是将申请内存与构造函数分离开来处理,该allocator只负责申请和销毁空间, 采用原始maolloc和free函数管理内存,自己动手写异常处理函数 */ #ifndef __MALLOC_ALLOC_TEMPLATE_H_INCLUDED #define __MALLOC_ALLOC_TEMPLATE_H_INCLUDED #include<cstdlib> #include<cstddef> namespace juine { class __malloc_alloc_template { //typedef void (*set_alloc_handle)(); private: typedef void (*handle)(); static handle set_alloc_handle; //异常处理函数指针,由用户制定设定如何解决异常 static void* oom_malloc(size_t n);// 当分配内存失败的时候,调用该函数重新分配内存 public: static void* alloc(size_t n) { void *temp=malloc(n); temp=0; //故意的,目的是为了模拟出out of memory时该做些什么 if(!temp) temp=oom_malloc(n); return temp; } static void dealloc(void* buff,size_t /*n*/) { free(buff); } static void set_function_handle( void (*temp)()) { set_alloc_handle=temp; } static handle& get_function_handle() { return set_alloc_handle; } }; void (*__malloc_alloc_template::set_alloc_handle)()=0;// 这个必须写 不然下面函数中将显示set_alloc_handle没有定义 void* __malloc_alloc_template::oom_malloc(size_t n) { void *temp=0; while(!temp) { if(set_alloc_handle==0) { std::cout<<"you must find a methods to solve the memory problem "<<std::endl; //抛出异常元素 exit(1); } set_alloc_handle(); temp=malloc(n); } return temp; } } #endif // __MALLOC_ALLOC_TEMPLATE_H_INCLUDED
接下来是次级空间配置源码:
/* ** __default_alloc_template.h文件 次级空间配置器 */ #ifndef __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED #define __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED #include<cstddef> // for size_t #include<cstdlib> using std::cout; using std::endl; namespace juine { enum {__ALIGN=8}; enum {__MAX_BYTES=128}; enum {MAXSIZE=__MAX_BYTES/__ALIGN}; union obj { obj *next; char client_data[1]; }; class __default_alloc_template { public: static char *start_pool; //尽管为静态变量,初始化也应该在内的外面 static char *end_pool; //为char指针是为了刚好取得1byte内存 static size_t poolSize; static obj *free_list[MAXSIZE]; //将n转化为8的倍数 static size_t ROUND_UP(size_t n) { return (((n)+__ALIGN-1)&~(__ALIGN-1)); } //获得所在数组的下标 static size_t POSITION(size_t n) { return (((n)+__ALIGN-1)/__ALIGN-1); } static size_t poolLeftSize() { return end_pool-start_pool; } //当free_lisk指针为0时,应该从内存池取出内存挂在在指针下面 //,返回给用户的所需的内存地址(刷新指针数组所指的内存分布) static void* refill(size_t n) //此处n已是8的倍数 { size_t objs=20; char *chunk=chunk_alloc(n,objs); if(objs==1) return chunk; obj *result,*current_obj,*next_obj; result=(obj*)chunk; //这一块是用来返回给客户端的 //接下来的是将获得的内存节点将其串起来,让以后分配更加方便 free_list[POSITION(n)]=next_obj=(obj*)(chunk+n); size_t i; for(i=1;;i++) { current_obj=next_obj; next_obj=(obj*)((char*)current_obj+n); if(objs-1==i) { current_obj->next=NULL; break; } current_obj->next=next_obj; } std::cout<<"分配客户端后,"<<n<<"bytes大小的块还剩下:"<<i<<std::endl; return result; } //分配内存池空间,然后返回指向数组指针的首地址 static char* chunk_alloc(size_t n,size_t &objs) { char *result=NULL; size_t totalSize=n*objs; if(poolLeftSize()>totalSize) //当内存池够大时一般是申请20块 { result=start_pool; start_pool+=totalSize; cout<<"内存空间够大:得到"<<n<<"bytes块20个"<<endl; return result; //只是返回首地址而已 } else if(poolLeftSize()>=n) //当内存池大于申请内存大小时 { objs=poolLeftSize()/n; result=start_pool; start_pool+=objs*n; cout<<"内存空间比较大:得到"<<n<<"bytes块"<<objs<<"个"<<endl; return result; } else { //需要申请的内存 size_t size_bytes_to_get=objs*n*2+ROUND_UP(poolSize>>4); std::cout<<"需要申请的内存大小为:"<<size_bytes_to_get<<std::endl; if(poolLeftSize()>0) //当内存池中还有零头时,将其分配出去 { obj *temp; temp=free_list[POSITION(poolLeftSize())]; //所剩内存一定是一个块! free_list[POSITION(poolLeftSize())]=(obj*)start_pool; free_list[POSITION(poolLeftSize())]->next=temp; start_pool=end_pool=NULL; } start_pool=(char *)malloc(size_bytes_to_get); end_pool=start_pool+size_bytes_to_get; result=start_pool; start_pool+=n*objs; poolSize+=size_bytes_to_get; return result; } } public: static void *alloc(size_t n) //n为申请的内存大小 { obj *result=NULL; //大于128时调用第一级配置器 if(n>(size_t) __MAX_BYTES) { return (malloc_alloc::alloc(n)); } //得到在free_list所在的位子 result=free_list[POSITION(n)]; if(result==NULL) { //调用refill,从内存池中来获得内存,将其挂到链表之下 void *r=refill(ROUND_UP(n)); return r; } cout<<"指针数组还有内存,已得到指针大小"<<endl; free_list[POSITION(n)]=result->next; return result; } //释放内存 static void dealloc(void *buff,size_t n) { if(n>(size_t) __MAX_BYTES) { malloc_alloc::dealloc(buff,n); } int pos=POSITION(n); ((obj*)buff)->next=free_list[pos]; free_list[pos]=(obj*)buff; } //无意义,纯粹为了标准接口 typedef void (*handle)(); static handle& get_function_handle() { handle p; return p; } }; //初始化数据 char* __default_alloc_template::start_pool=NULL; char* __default_alloc_template::end_pool=NULL; size_t __default_alloc_template::poolSize=0; obj* __default_alloc_template::free_list[MAXSIZE]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; } #endif // __DEFAULT_ALLOC_TEMPLATE_H_INCLUDED
对空间适配器进行统一的封装,对外提供统一的接口
/* simple_alloc.h ** 该类实现对外统一的接口,其中配置器应该使用了单例模式,所有函数,变量都是static变量 */ #ifndef SIMPLE_ALLOC_H_INCLUDED #define SIMPLE_ALLOC_H_INCLUDED #include<typeinfo> //for typeid #include<new> //for placement new #include"__malloc_alloc_template.h" typedef juine::__malloc_alloc_template malloc_alloc; //注意一下,只能这样写, 顺序很重要 #include"__default_alloc_template.h" typedef juine::__default_alloc_template default_alloc; // 指定配置器的类型,选择哪一个来进行配置 #ifdef _USE_ALLOC typedef malloc_alloc my_alloc; #else typedef default_alloc my_alloc; #endif namespace juine { // 该函数用来表示使用第一配置器时,当不给定out of memory处理方法时,则默认使用如下函数(指针) void fun(){std::cout<<"use default method to solve oom!"<<std::endl;} void (*temp)()=fun; template<class T,class Alloc=my_alloc> class simple_alloc { public: //对ALLOC其进行封装,对外提供统一的接口 T* alloc(size_t n) { T* temp=(T*)Alloc::alloc(n); return temp; } T* alloc() { T* temp=(T*)Alloc::alloc(sizeof(T)); return temp; } void dealloc(T* &buff) { Alloc::dealloc(buff,sizeof(T)); buff=NULL; //释放内存后,指针要归0 } void dealloc(T* &buff,size_t n) { Alloc::dealloc(buff,n*sizeof(T)); buff=NULL; } //制定异常处理机制(但是只针对第一种配置情况) simple_alloc() { if(typeid(Alloc)==typeid(malloc_alloc)) //当为次级配置时,为了是不报错,提供了一个无意义的接口 Alloc::get_function_handle()=temp; } }; //构造函数工厂 template<class T,class T2> inline void construct(T *p,T2 value) { new(p) T(value); //placement new 的用法 } //析构函数工厂 template<class T> inline void destroy(T *p) // 为了简单,就只写一个destroy { p->~T(); } } #endif // SIMPLE_ALLOC_H_INCLUDED
// test_alloc.cpp #include <iostream> #include<cstring> #include<cstdlib> #define _USE_ALLOC //用过是否定义这个变量,确定使用的是哪个配置器 #include"simple_alloc.h" using namespace std; using namespace juine; struct student { int id; string name; student(int _id,string _name):id(_id),name(_name){} }; ostream& operator<<(ostream &os,const student& ss) { os<<"id:"<<ss.id<<" "<<"name:"<<ss.name; return os; } int main(void) { simple_alloc<student> ss; student *p=ss.alloc(3*sizeof(student)); student *q=ss.alloc(3*sizeof(student)); student *t=ss.alloc(3*sizeof(student)); //for(;i<3;i++) //次级配置中的一个缺陷,其实p指针应该只是指向5个int的,不知到怎么判断越界了没 construct(p,student(1,"小zhang")); //用来构造对象 construct(p+1,student(2,"小lan")); construct(p+2,student(3,"小s")); int i; for(i=0;i<3;i++) cout<<p[i]<<endl; destroy(p); cout<<p[0]<<endl; }
其测试结果如下:
当时用第一及空间配置器时结果如下:
使用次级配置空间时:
最后结果基本达到预期的想法,实现了空间配置器。
下次,接下来是迭代器的实现!