• 分配器内存一步一步写STL:空间配置器(1)


    查了好多资料,发现还是不全,干脆自己整理吧,最少保证在我的做法正确的,以免误导读者,也是给自己做个记录吧!

        侯捷说:追踪一流程序,并从中吸取养分,模拟着他写的程序,比那些自以为靠自己努力写出来的下三流程序代价高得多,最少我这么认为——世界上99.999%的程序,在STL眼前都是下三流水平!

        侯捷老师这句话对STL的评价太高了,以前只是熟练使用STL,知道一点原理,受他的影响,最终还是决议研讨STL的源码,收益颇丰,还是把这个过程记录下来,并想着借助标准库的原理,自己写一个完整的仿STL,我觉得这是一个很大的工程,因为涉及到高等数据结构,强大的算法,泛型编程思维,STL的掌握,强大的C++编码水平,层次庞杂的继承,型别萃取技能,相信这个过程会收益颇丰!毕竟我才大二,时光一大把,我想在我本科期间,做完这一个工程,也无憾了!

        下图就是STL的层次分布图,当然还缺乏一些组件,比如数值处置,pair对组,string,智能指针以及valarray数组等等,其中实现的难点重要集中在几个地方,分别是map红黑树的实现,heap算法体系,函数配接器,流迭代器。尤其是函数配接器,他的外部实现简直是穷尽一切顶尖技能,令人叹为观止,我先从最左边的内存分配器开始,因为他是全部的核心!

        分配器和内存

        首先STL的内存分配器(空间配置器)在标准库中充当了异常重要的角色,全部的内存分配,管理,释放都是由他控制,SGI的设计理念就是把内存管理这一块红灯区抽离出来,作为模版参数传递进去每个容器,

        比如在vector:

        template<class T,class Alloc<T> = allocator<T> >

        class vector.........

        他使用的是内置的默许内存分配器,在上图中我们看到有两个分配器,这是SGI STL中的高等之处,实作了多级内存分配,利用内存池实现效率上的优化,同时也减少了内存碎片的可能性。

        在这之前须要知道两个全局函数 ::operator new 和 ::operator delete ,注意不要把他们和一般的new delete混为一谈,我们的运算符new在分配内存的时候同时调用对象的结构函数初始化内存,而::operator new只是分配内存,并不调用结构函数,这是实现一块无初始化内存池的关键点,同理delete。

        另外还须要了解placement new运算符,他是定位运算符,并不分配内存,只是定位到某一已分配区域!

        这里我们先实现一个能跟标准容器接口的分配器类,他并不高明,但是表现了标准分配器的必要特性,其实从某个角度说属于SGI版本的一级分配器:

    template<class _Ty>
    	struct Allocator_base
    	{	//配置器基类
    	typedef _Ty value_type;
    	};
    
    template<class _Ty>
    	struct Allocator_base<const _Ty>
    	{	//配置器特化于const的基类
    	typedef _Ty value_type;
    	};
    
    template<class _Ty>
    class Allocator
    		:public Allocator_base<_Ty>
    {
    public:
    	//配置器外部型别
    	typedef typename std::size_t size_type;
    	typedef typename std::ptrdiff_t difference_type;
    	typedef _Ty* pointer;
    	typedef const _Ty* const_pointer;
    	typedef _Ty& reference;
    	typedef const _Ty& const_reference;
    	typedef Allocator_base<_Ty> _Mybase;
    	typedef typename _Mybase::value_type value_type;
    
    	//配置器型别转换
    	template<class _other>
    	struct rebind
    	{
    		typedef Allocator<_other> other;
    	};
    
    	//地址函数定义
    	pointer address(reference value)const{
    		return &value;
    	}
    	const_pointer address(const_reference value)const{
    		return (const_pointer)&value:
    	}
    
    	//默许结构函数 什么都不干
    	Allocator() throw() {}
    	//默许复制结构 
    	Allocator(const Allocator &)throw() {}
    	//不同配置器的复制结构
    	template<class _otherAll>
    	Allocator(const Allocator<_otherAll> &)throw() {}
    
    	//析构函数
    	~Allocator()throw() {}
    	
    	//返回能分配的最大内存
    	size_type max_size()const throw()
    	{   //借助数值函数
    		numeric_limit<size_type>::max() /sizeof(_Ty);
    	}
    	//分配未结构的内存待用
    	pointer allocate(size_type num,
    		typename Allocator<void>::const_pointer hint= 0)
    	{
    		return (pointer)(::operator new(num * sizeof(_Ty)) );
    	}
    	//在内存中结构对象
    	void construct(pointer p,const_reference value)
    	{
    		new(p) _Ty(value);
      	}
    	//析构内存中的对象
    	void destroy(pointer p)
    	{
    		p->~_Ty();
    	}
    
    	void deallocate( pointer p, size_type size )
    	{
    		::operator delete(p);
    	}
    	//为了跟标准配置器接轨,这里只能返回true,下一个只能返回false
    	bool operator==(Allocator const& a) const 
    	{ return true; }
    
    	bool operator!=(Allocator const& a) const 
    	{ return !operator==(a); }
    };
    
    
    //allocator模版特化于类型void的类
    template<> class Allocator<void>
    {
    public:
    	typedef void _Ty;
    	typedef _Ty* pointer;
    	typedef const _Ty* const_pointer;
    	typedef _Ty value_type;
    
    	template<class _other>
    	struct rebind
    	{
    		typedef Allocator<_other> other; 
    	};
    
    	Allocator()throw() 
    	{ //还是一样,什么都不干
    	}
    
    	Allocator(const Allocator<_Ty>& )throw()
    	{ //复制结构,什么都不干
    	}
    
    	template<class _Other>
    		Allocator(const Allocator<_Other>&) throw()
    		{	
    		}
    	template<class _Other>
    		Allocator<_Ty>& operator=(const Allocator<_Other>&)
    		{	
    		return (*this);
    		}
    
    };
        每日一道理
    喜马拉雅直冲霄汉,可上面有攀爬者的旗帜;撒哈拉沙漠一望无垠,可里头有跋涉者的脚印;阿尔卑斯山壁立千仞,可其中有探险者的身影;雅鲁藏布江湍急浩荡,可其中有勇敢者的故事。

        
    最开始是两个基类,这两个基类没有任何成员,只有一个内置型别,这两个基类不是必须的,可以略过,只是表现一种好的设计而已,最后一个类是模版特化了一个void类型,这样做也只是为了使用void的时候不发生未定义行为,从这一点可以看到STL对各种可能的情况都做了充分的预感,我们重要来看Allocator类!

        刚开始定义了一对typedef,这是为分配器类定义一堆内置型别,其实也可以不定义啊,只不过在STL中都这样定义,构建出一种同一的类型型别,便利管理和可读性

        接下来的

        template<class _other>

        struct rebind

        {

         typedef Allocator<_other> other;

        };

        这是一个分配器转换方法,可以便利的转换为为另一种类型服务的分配器,比如说我们结构的是T类,如果须要结构T*的时候,可以这样使用

        Allocator<T>::rebind<T*>::other  pAllocator;

        这样pAllocator就是一个为T*服务的分配器,详细参考《STL标准程序库》!

        该类接下来的函数都是标准接口必须的,不能少任何一个,其中有变动的是这四个 allocate   deallocate   destory  construct

        allocate函数分配一片连续的未被结构的空间备用,

        deallocate  函数释放空间

        construct函数调用布局new,同时调用结构函数,对象被new定位在指定位置

        destory 函数调用析构函数,

        之所以说这几个函数可变性比较大,我举个例子,假如我们做一个学生成就管理系统,当然须要结构学生类,也就是须要从数据库获得数据来初始化学生对象,那么你就能够在construct里头内嵌SQL语句,在你艳服学生对象的容器中,获得的数据起源不是从键盘输入(参数传入),而是主动的从数据库获得过来,这样岂不是很便利!同理其他几个函数,

        这个分配器还是很简单的,就只须要注意那几点,所以我们在写自己的分配器希望和标准容器接口时,须要注意这几点

        当前还会使用这个分配器,直接拿来当作SGI STL版本的一级分配器,至于二级分配器,下一节在写!

        到此我们可以写个小程序测试一下了:

    #include <iostream>
    #include <list>
    #include <vector>
    #include <algorithm>
    #include "allocator.h"
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	std::vector<double,Allocator<double> > vec_double;
    	std::list<int,Allocator<int> > list_int;
    	for(int i=0;i<60;i++)
    	{
    		list_int.push_back(i);
    		vec_double.push_back( double(i)/3 );
    	}
    	
    	list<int,Allocator<int> >::iterator it = list_int.begin();
    	vector<double,Allocator<double> >::iterator io = vec_double.begin();
    
    	cout<<"list test:"<<endl;
    	for(; it!= list_int.end();++it)
    		cout<<*it<<" ";
    	cout<<endl<<endl;
    
    	cout<<"vector test:"<<endl;
    	for(;io!= vec_double.end();++io)
    		cout<<*io<<" ";
    
    	system("pause");
    	return 0;
    }

        分配器和内存

        
     

    文章结束给大家分享下程序员的一些笑话语录: 祝大家在以后的日子里. 男生象Oracle般健壮; 女生象win7般漂亮; 桃花运象IE中毒般频繁; 钱包如Gmail容量般壮大, 升职速度赶上微软打补丁 , 追女朋友像木马一样猖獗, 生活像重装电脑后一样幸福, 写程序敲代码和聊天一样有**。

    --------------------------------- 原创文章 By
    分配器和内存
    ---------------------------------

  • 相关阅读:
    Android应用开发之避免内存泄露
    史上最经典的数据库面试题之二
    某大型银行深化系统之二十一:Log4j执行性能
    ruby支持批量数组的定义
    为VIM提供python代码提示功能
    使用win7登陆远程机器时自动保存密码
    安装Beanstalk
    在linux下安装或者卸载nginx
    python的数据类型
    使Ruby自动定位查找本地路径
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3109150.html
Copyright © 2020-2023  润新知