内存池实现
内存管理:是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。
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;
}
这样我们就可以实现一个类使用一个内存池了