空间配置器实现

  STL容器可以保存任何C++语言支持的基本类型、用户自定义类型的对象。容器本身只是实现了一种数据结构,对用户数据进行管理。用户数据最终都是保存在内存中的,那么内存的申请、释放工作是由“空间配置器”承担的。标准c++中都提供了std::allocator类。

     当容器中保存的是用户自定义类型数据时,有的数据类型结构简单,占用的空间小;而有的数据类型结构复杂,需要的内存空间大。有的应用程序,需要频繁的进行数据元素的插入、删除操作,这就对应这频繁的内存空间的申请、释放工作。大家都知道频繁的内存操作,会产生严重的性能问题。为了解决这些问题,stlport提供了两个空间配置器。一个是“简单空间配置器”,只是对c运行时库中malloc/free函数进行了简单的封装,唯一不同的是,提供类似new异常处理机制。另一个是“基于内存池的空间配置器”,容器每次申请内存的时候,空间配置器会基于一定的策略,向os申请交大的内存,避免每次内存申请都向os申请。

     下面用图片说明STL具有次级空间配置能力。

空间配置器实现_第1张图片

空间配置器实现_第2张图片

根据上图说明,我们可以写出很简陋地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;
}


其测试结果如下:

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

空间配置器实现_第3张图片

使用次级配置空间时:

空间配置器实现_第4张图片

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

下次,接下来是迭代器的实现!

你可能感兴趣的:(空间配置器实现)