转自:http://blog.csdn.net/zhuweisky/article/details/416415
我们知道,C++中当我们用new在堆中创建一个对象时,会发生两件事情。首先调用operator new分配一定大小的内存空间,然后在此空间上调用构造函数以形成对象。而operator new涉及到寻找合适内存的算法,往往,这个算法是比较费时间的,所以,如果我们的程序中需要不断的产生和释放某类型的对象,那么operator new所累计起来的时间可能就会成为阻碍程序性能提高的一个重要因素了。看来我们确实有必要为这种情况提供一种恰当的方案以实现快速内存分配。
先看看我们所处的环境。我们有一个自定义的类型(即一个class),而我们的程序需要不断的产生和释放此类型的对象,而产生此对象时需要的operator new是比较费时间的。我们将在如何缩小operator new操作的时间这个问题上努力?再深入一步想,我们用new分配内存生成对象,然后释放该内存,接着又重复同样的故事。呵,你可能有方案了,是吗?
是的,我们可以在释放内存的时候,并不是真的释放内存,而是将指向该内存的指针维护起来,等到需要分配内存,就不用再调用内存分配算法了,直接将维护的指针返回就行了。我们可以将这中策略封装在一个基类MemoryManage中,如果你的自定义类比如Derived类需要采用这种内存分配方案,那么让Derived公共继承MemoryManage就行了。
我们可以用一个内存块链表来维护被释放的内存块。根据“IS_A”关系,即一个Derived对象就是一个MemoryManage对象,所以我们可以在MemoryManage类中添加一个(MemoryManage*)型的指针,让其指向下一块需要维护的内存。另外我们需要一个MemoryManage对象来维护这个内存块链表的head,这可以使其成为MemoryManage类的一个静态成员。内存块链表的布局大致如下图。
接下来,我们要重写operator new和operator delete ,以覆盖掉默认的内存分配方式。MemoryManage类的主要代码如下。
class MemoryManage
{
public:
static MemoryManage* listhead ;
MemoryManage* next ;
MemoryManage(){ next = NULL ; }//设定next指针初值
void* operator new(size_t size) //重定义operator new
{
if(listhead == NULL)
{
return malloc(size) ;
}
else
{
void* p = (void*)listhead ;
listhead = listhead->next ;
cout<<"从链表中提出一个指针返回!"<<endl ;
return p ;
}
}
void operator delete(void* pp) //重定义operator delete
{
MemoryManage* pp1 = (MemoryManage*)pp ;
pp1->next = listhead ;
listhead = pp1 ;
cout<<"已经将需要释放的空间添加到链表中!"<<endl ;
}
};
MemoryManage* MemoryManage::listhead = NULL ; //初始化静态成员
如果我们想当链表中维护的内存个数超过一定数量N(比如50)时,如果还有对象要释放,我们就不将其内存添加到链表中了,而是直接释放掉。这可以在MemoryManage类中增加一个静态成员count来实现。
首先,在MemoryManage类中声明这个成员,然后初始化为0。
static int MemoryManage ::count = 0 ;
然后改写operator new和operator delete。
void* operator new(size_t size) //重定义operator new
{
if(count == 0) //相当于listhead == NULL ;
{
return malloc(size) ;
}
else
{
void* p = (void*)listhead ;
listhead = listhead->next ;
count-- ; // 减小链表中维护的内存块计数
cout<<"从链表中提出一个指针返回!"<<endl ;
return p ;
}
}
void operator delete(void* pp) //重定义operator delete
{
if(count == 50)
{
free(pp) ;
}
else
{
MemoryManage* pp1 = (MemoryManage*)pp ;
pp1->next = listhead ;
listhead = pp1 ;
count++ ; //增加链表中维护的内存块计数
cout<<"已经将需要释放的空间添加到链表中!"<<endl ;
}
}
我们似乎应该提供一个函数,让用户在适当的时候,可以释放掉内存块链表中维护的所有内存。于是在MemoryManage类中增加一个静态的freeAllSpace函数。如下
static void freeAllSpace(void)
{
MemoryManage* temp = NULL;
int i= 0 ;
while(listhead != NULL)
{
temp = listhead->next ;
free(listhead) ;
cout<<"第"<<(++i)<<"次释放空间"<<endl ;
listhead = temp ;
}
}
似乎一切都很好的解决了,是吗?好,来看看下面的例子。
class Student:public MemoryManage
{
private:
string name ;
public:
Student(string ss):name(ss){}
void display(){ cout<<"name: "<<this->name<<endl ; }
};
class Teacher:public MemoryManage
{
private:
string name ;
int age ;
public:
Teacher(string ss ,int aa):name(ss),age(aa){}
void display()
{
cout<<"this is a teacher , name:"<<this->name<<endl ;
cout<<"age: "<<this->age<<endl ;
}
};
void main(void)
{
Student* s1 =new Student("qiqi") ;
s1->display() ;
Student* s2 = new Student("wei") ;
s2->display() ;
delete s1 ;
delete s2 ;
Teacher* s3 = new Teacher("sky",42) ;
Teacher* s4 = new Teacher("feifei",36) ;
s3->display() ;
s4->display() ;
delete s3 ;
delete s4 ;
Student::freeAllSpace() ; //产生运行期内存错误!
Teacher::freeAllSpace() ; //产生运行期内存错误!
}
运行一下,得到了什么结果?输出好像是没有什么问题,而实际上内存管理已变得相当混乱。注意到sizeof(Teacher)比sizeof(Student)大,而在程序执行的过程中,我们将只有sizeof(Student)大小的空间分配给了一个大小为sizeof(Teacher)的Teacher对象,这肯定会覆盖掉原空间相邻内存中的其它数据,如果被覆盖掉的正好是重要的信息,那么程序的运行结果是可想而知的。
难道没有办法了吗?如果我们Student对象由一个链表来管理,而Teacher对象由另一个链表来管理,而且所有一切需要按此策略分配的类型的对象都由它自己的特定链表来管理,那就可以了啊。怎么做了?其实我们只要将MemoryManage类声明为模板就可以了,像这样:
template<class T>
class MemoryManage{... ...} //其它一切保持不变!
另外静态成员的初始化要修改一下:
template<class T>
MemoryManage<T>* MemoryManage<T>::listhead = NULL ;
template<class T>
static int MemoryManage ::count = 0 ;
然后定义Student类和Teacher类时,让它们以自己类型名作为模板参数从MemoryManage继承就可以了,像这样:
class Student:public MemoryManage<Student>{... ...} //其它一切不变!
class Teacher:public MemoryManage< Teacher >{... ...} //其它一切不变!
再执行一下上面的main函数,你会发现已经可以完全正确运行了。因为现在每一个类(如Student或Teacher)都有它自己的内存管理链表,这个链表中所有的指针类型是完全一样的,当然,它们所指向的内存块的大小也是一样的。如此以来,前面出现内存管理混乱的问题就解决了。上面的解决方案很简单,仅仅是将MemoryManage类声明为模板类就可以了。的确是很简单,但是这后面隐藏的思想却极具价值。在这里再继续深入讨论这个问题就有离题的意味了,对于这个话题笔者也许可以在下一篇文章中另行讨论。
只要我们在进行程序设计,特别是在进行系统级的程序设计时,内存管理是需要我们给予足够关注的一个方面,其中一个基本的原则就是“时空互换”原则,也就是我们在设计时应权衡是用空间换取时间(即为了提高运行的速度,而对内存的需求加大),还是反过来,用时间换取空间。本文所介绍的这种内存管理方法就是典型的用空间换时间的处理方法。灵活地使用“时空互换”原则,我们可以自己设计出多种内存管理方案和其对应的算法。