您的位置:首页 > 其它

空间配置器实现

2014-04-04 10:54 183 查看
  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;
}


其测试结果如下:

当时用第一及空间配置器时结果如下:



使用次级配置空间时:



最后结果基本达到预期的想法,实现了空间配置器。

下次,接下来是迭代器的实现!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: