内存池实现

内存管理:是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。

C++下内存管理是让几乎每一个程序员头疼的问题,分配足够的内存、追踪内存的分配、在不需要的时候释放内存——这个任务相当复杂。而直接使用系统调用new/delete进行内存分配和释放,有以下弊端:

1.调用malloc/new,系统需要根据“最先匹配”、“最优匹配”或其他算法在内存空闲块表中查找一块空闲内存,调用delete,系统可能需要合并空闲内存块,这些会产生额外开销;

2.频繁使用时会产生大量内存碎片,从而降低程序运行效率;

3.容易造成内存泄漏;

4:调new和delete是一个系统调用过程,要进行用户态和内核态的切换,效率是很低下的

内存池(memory pool)是代替直接调用new/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,其优势在于:

1.比new/delete进行内存申请/释放的方式快;

2.不会产生或很少产生堆碎片;

 3.可避免内存泄漏;

内存池(memory)作为管理进程内存的方式之一,我们有必要弄清楚它的工作原理。

对于这个内存池要如何写我们先来看看思路:

我们首先要申请一块大内存,当然,这个内存的大小你自己规定,但是必须是new的对象的大小加上它的指针域总大小的倍数,然后将其初始化为如下图所示,用pool标识未被使用的数据节点,当有类要申请对象(即new)时我们就将pool指向其下一个位置,直到pool等于NULL,再次开辟一块大内存。这样就不用每次new/delete进行用户态到内核态的切换了。如果我们释放一个节点对象,只需将该节点的next域指向pool,然后将pool重置为我们释放的这个节点,即将其连接上了。

      

 

简单内存池的实现:

#include<iostream>
using namespace std;

const int MemPoolSize=10; //开辟内存池QueueItem对象的个数

/*
        简单内存池,只能new QueueItem,采用的是链式队列举例子
	operator new 只开辟内存  系统上也存在,我们可以重写,不用默认的
	new operator 是new,new就是先调用operator new然后再构造   
	operator delete 只释放内存
	delete operator 就是系统的delete ,实际上delete就是先调用了析构再调用operator new
        所以实现内存池就是将重写operator new,使得它每次都在我们的mempool中申请空间
*/

template<typename T>
class Queue   //带头节点链式队列类
{
public:
	Queue()
	{
		pfront=prear=new QueueItem();  //我们new的是QueueItem,所以要在QueueItem类中重载operator new; 
	}
	~Queue()
	{
		QueueItem *pcur=pfront;
		QueueItem *pnext=pfront;
		while(pcur!=NULL)
		{
			pnext=pcur->pnext;
			delete pcur;
			pcur=pnext;
		}
		pfront=prear=NULL;
	}
	void push(T val)
	{
		QueueItem *pnewnode=new QueueItem(val);
		prear->pnext=pnewnode;
		prear=pnewnode;
	}
	void pop()
	{
		if(!Is_Empty())
		{
			QueueItem *pcur=pfront->pnext;
			pfront->pnext=pcur->pnext;
			delete pcur;
		}
	}
	T front()
	{
		if(!Is_Empty())
		{
			return pfront->pnext->mdata;
		}
	}
	T back()
	{
		if(!Is_Empty())
		{
			return prear->mdata;
		}
	}
	void Show()
	{
		if(!Is_Empty())
		{
			QueueItem *pcur=pfront->pnext;
			while(pcur!=prear)
			{	
				cout<<pcur->mdata<<" ";
				pcur=pcur->pnext;
			}
			cout<<pcur->mdata<<endl;
		}
	}
private:
	bool Is_Empty()
	{
		return pfront==prear;
	}
	class QueueItem   //节点类
	{
	public:
	    QueueItem(T val=T()):mdata(val),pnext(NULL){}
	    void *operator new(size_t size)  //返回值和参数类型是固定写法
            {
	        if(pool==NULL)  
		{
		    pool=(QueueItem *)new char[size * MemPoolSize]();  //内存池一次开10个QueueItem对象大小
		    QueueItem *pcur=pool;
		    while(pcur<pool+MemPoolSize-1)  //最终让pcur指向池中最后一个位置元素
		    {
			pcur->pnext=pcur+1;
			pcur++;
		    }
		    pcur->pnext=NULL;  //最后一个的指针域为空
		}
	        QueueItem *rt=pool;  
	        pool=pool->pnext;
	        return rt;   //返回一个链表节点
	    }
     	    void operator delete(void *ptr)
		{
			if(ptr==NULL)
			{
			    return;
			}
			QueueItem *p=(QueueItem *)ptr;  //void没有数据域和指针域,所以需要强转
			p->pnext=pool;
			pool=p;
		}
	private:
		T mdata;   //数据域
		QueueItem *pnext;  //指针域
		static QueueItem *pool;  //对于每一个节点都共有的,所以将pool设置为静态的
		template<typename T>
		friend class Queue;
	};
	QueueItem *pfront;    //队头指针,指向头节点
	QueueItem *prear;    //队尾指针,指向最后一个有效元素节点
};
template<typename T>
typename Queue<T>::QueueItem *Queue<T>::QueueItem::pool=NULL;
//系统也有默认的,operator new只申请内存,但是会进行系统调用,效率低,所以进行内存池

int main()
{
	Queue<int> que;
	for(int i=0;i<15;i++)
	{
	    que.push(i+1);
	}
	que.pop();
	que.Show();
	cout<<que.front()<<endl;
	cout<<que.back()<<endl;
}

上面代码我们用一个带头节点的链式队列申请内存池中的元素,但是我们发现,我们这个内存池只能new一个QueueItem的对象,并不具有通用性,所以这个其实是没有什么意义的,只是为了让大家清楚的认识我们内存池,接下来我为大家写一个通用的内存池。

#include<iostream>
using namespace std;

const int MemSize=10;  //内存池中节点个数

template<typename T>  //内存池类
class CMemPool
{
public:
	void *alloc(size_t size)  //给外部一个接口,申请到外部想要申请的内存大小
	{
		if(pool==NULL)
		{
			pool=(Node *)new char[(size+4)*MemSize];  //size是传过来的数据域,我们还需要4个字节的next域
			Node *pcur=pool;
			for(;pcur<pool+MemSize-1;pcur++)
			{
				pcur->pnext=pcur+1;
			}
			pcur->pnext=NULL;
		}
		Node *rt=pool;  
		pool=pool->pnext;
		return rt;
	}
	void dealloc(void *ptr)
	{
		if(ptr==NULL)
			return;
		Node *p=(Node*)ptr;
		p->pnext=pool;
		pool=p;
	}
private:
	class Node
	{
	public:
		Node(T val):mdata(val),pnext(NULL){}
	private:
		T mdata;  //这个T实际上就是我们的Student
		Node *pnext;
		template<typename T>
		friend class CMemPool;
	};
	static Node* pool;  //pool的类型无法获取到,所以我们就给其传一个定义的通用性Node *
};
template<typename T>
typename CMemPool<T>::Node *CMemPool<T>::pool=NULL;  静态变量类外初始化

class Student
{
public:
	Student(string name,int age,bool sex):
	  mname(name),mage(age),msex(sex){}
	  void *operator new(size_t size)
	  {
		  return mpool->alloc(size);  //用一个接口,并不是直接在自身类中重写operator new,这样就没有通用性了。
	  }
	  void operator delete(void *ptr)
	  {
		  mpool->dealloc(ptr);
	  }
private:
	string mname;
	int mage;
	bool msex;
	static CMemPool<Student> *mpool;  //为了要访问到内存池中的alloc和dealloc接口,就必须给一个内存池对象,同一类共用一个mpool指针
};

CMemPool<Student> *Student::mpool=new CMemPool<Student>();  //类外申请一个内存池对象


int main()
{
	Student *pstu1=new Student("zhangsan",18,true);
	Student *pstu2=new Student("lisi",19,true);
        CMemPool<Student> *pmpool1=new CMemPool<Student>();  //我们发现还会重新生成内存池对象
	CMemPool<Student> *pmpool2=new CMemPool<Student>();
	return 0;	
}

对于我们同一个类中的对象,我们只希望有一个共有的内存池,但是我们这个pmpool1和pmpool2显然是开了一个新的内存池对象,我们可以看一下:

可以看出内存是不一样的,所以我们如何让同一个类只用一个内存池呢,那么我们就要将内存池写成单例模式。

内存池单例模式代码(实际上就小小改动一点啦)

#include<iostream>
using namespace std;

//将内存池设计成单例模式,使得一个类产生一个对象
const int MemSize=10;
template<typename T>
class CMemPool
{
public:
	static CMemPool<T> *GetInstance()  //static不依赖对象,指针或者引用不生成临时量
	{
		return &mempool;
	}
	void *alloc(size_t size)
	{
		if(pool==NULL)
		{
			pool=(Node *)new char[(size+4)*MemSize];  //size是传过来的数据域,我们还需要4个字节的next域
			Node *pcur=pool;
			for(;pcur<pool+MemSize-1;pcur++)
			{
				pcur->pnext=pcur+1;
			}
			pcur->pnext=NULL;
		}
		Node *rt=pool;
		pool=pool->pnext;
		return rt;
	}
	void dealloc(void *ptr)
	{
		if(ptr==NULL)
			return;
		Node *p=(Node*)ptr;
		p->pnext=pool;
		pool=p;
	}
private:
	CMemPool(){}
	CMemPool(const CMemPool &src);  //单例模式拷贝构造函数不用实现,写个声明就行
	class Node
	{
	public:
		Node(T val):mdata(val),pnext(NULL){}
	private:
		T mdata;  //这个T实际上就是我们的Student
		Node *pnext;
		template<typename T>
		friend class CMemPool;
	};
	static CMemPool<T> mempool;  //静态成员对象
	static Node* pool;
};
template<typename T>
typename CMemPool<T>::Node *CMemPool<T>::pool=NULL;
template<typename T>
CMemPool<T> CMemPool<T>::mempool;

class Student
{
public:
	Student(string name,int age,bool sex):
	  mname(name),mage(age),msex(sex){}
	  void *operator new(size_t size)
	  {
		  return mpool->alloc(size);
	  }
	  void operator delete(void *ptr)
	  {
		  mpool->dealloc(ptr);
	  }
private:
	string mname;
	int mage;
	bool msex;
	static CMemPool<Student> *mpool;  //静态成员要在类外初始化,因为其不依赖于对象
};

CMemPool<Student> *Student::mpool=CMemPool<Student>::GetInstance();//不能通过new来构造了


int main()
{
	Student *pstu1=new Student("zhangsan",18,true);
	Student *pstu2=new Student("lisan",19,false);

	CMemPool<Student> *pmpool1=CMemPool<Student>::GetInstance();
	CMemPool<Student> *pmpool2=CMemPool<Student>::GetInstance();

	return 0;	
}

这样我们就可以实现一个类使用一个内存池了 


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值