轉自: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類聲明為模板類就可以了。的確是很簡單,但是這後面隱藏的思想卻極具價值。在這裡再繼續深入討論這個問題就有離題的意味了,對於這個話題筆者也許可以在下一篇文章中另行討論。
只要我們在進行程式設計,特別是在進行系統級的程式設計時,記憶體管理是需要我們給予足夠關注的一個方面,其中一個基本的原則就是“時空互換”原則,也就是我們在設計時應權衡是用空間換取時間(即為了提高啟動並執行速度,而對記憶體的需求加大),還是反過來,用時間換取空間。本文所介紹的這種記憶體管理方法就是典型的用空間換時間的處理方法。靈活地使用“時空互換”原則,我們可以自己設計出多種記憶體管理方案和其對應的演算法。