動態對象創建(二)重載new和delete
上文我簡單介紹了一下動態對象創建的方法,這一篇文章的內容主要是對重載new和delete做一些講解,也希望能夠得到博友們的指點,在這裡謝過大家。
通常我們為了一些目的而使用new和delete的內存分配系統,但是在特殊情況下,它並不能夠滿足需要。最常見的改變分配系統的原因是出於效率考慮:也許要創建和銷毀一個特定的類的非常多的對象以至於這個運算變成了速度的瓶頸。C++允許重載new和delete來實現我們自己的存儲分配方案,所以可以用它來處理問題。
另一個問題就是堆碎片:分配不同大小的內存可能會在堆上產生很多碎片,以至於很快用完內存。雖然內存可能還有,但是由於都是碎片,也就找不到足夠大的內存塊滿足需要。通過為特定類創建自己的內存分配器,可以確保這種情況不會發生。
當我們在重載operator new()和operator delete()時,我們只是改變了原有的內存分配方法。編譯器將用重載的new代替默認版本去分配內存,然後為那個內存調用構造函數。所以,雖然當編譯器看到new時,編譯器分配內存並調用構造函數,但是當重載new時,可以改變的只是內存分配部分。
接下來,我分為三個部分詳細講解重載new和delete:重載全局new和delete、對於一個類重載new和delete以及為數組重載new和delete。
當我們重載全局的new和delete的時候,可想而知,就會使默認版本完全不能被訪問--甚至在這個重新定義裡也不能調用它們。
重載的new必須有一個size_t參數。這個參數由編譯器產生並傳遞給我們,它是要分配內存的對象的長度。必須返回一個指向等於這個長度(或者大於這個長度)的對象的指針。如果沒有找到存儲單元(在這種情況下,構造函數不被調用),則返回一個0。然後如果找不到存儲單元,不能僅僅返回0,我們還應該調用new-handler或產生一個異常信息之類的事,告訴這裡出現了問題。
我們首先需要明白的一點就是:operator new()的返回值是一個void*,而不是指向任何特定類型的指針。所做的是分配內存,而不是完成一個對象建立--直到構造函數調用了才完成對象的創建,它是編譯器確保做的動作,不在我們的控制范圍之內了,所以我們就沒有必要考慮。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<algorithm> 7 #define inf 0x7fffffff 8 using namespace std; 9 10 void* operator new(size_t sz) 11 { 12 printf("operator new: %d Bytes\n",sz); 13 void* m = malloc(sz); 14 if (!m) puts("out of memory"); 15 return m; 16 } 17 18 void operator delete(void* m) 19 { 20 puts("operator delete"); 21 free(m); 22 } 23 24 class S 25 { 26 public: 27 S() {puts("S::S()"); } 28 ~S() {puts("S::~S()"); } 29 private: 30 int an[1000]; 31 }; 32 33 int main() 34 { 35 puts("creating & destroying an int"); 36 int* q = new int(23); 37 delete q; 38 puts("creating & destroying an int[]"); 39 int* p = new int[10](); 40 delete []p; 41 puts("creating & destroying an s"); 42 S* s = new S; 43 delete s; 44 puts("creating & destroying S[3]"); 45 S* sa = new S[3]; 46 delete []sa; 47 return 0; 48 }
為一個類重載new和delete的時候,盡管不必顯式的使用static,但是實際上仍是在創建static成員函數。它的語法也和重載任何其它運算符一樣。當編譯器看到使用new創建自己定義的類的對象時,它選擇成員版本的operator new()而不是全局版本的new()。但是全局版本的new和delete仍為所有其他類型對象使用(除非它們也有自己的new和delete)。這個和全局變量、局部變量的意思是一樣的,應該很好懂吧。
1 #include<iostream> 2 #include<cstddef> 3 #include<fstream> 4 #include<new> 5 using namespace std; 6 ofstream out("Framis.out"); 7 8 class Framis 9 { 10 public: 11 enum{psize = 100 }; 12 Framis() {out<< "Framis()" <<endl; } 13 ~Framis() {out<< "~Framis() ... " <<endl; } 14 void* operator new(size_t) throw (bad_alloc); 15 void operator delete(void*); 16 private: 17 enum{sz = 10 }; 18 char c[sz]; 19 static unsigned char pool[]; 20 static bool alloc_map[]; 21 }; 22 unsigned char Framis::pool[psize*sizeof(Framis)]; 23 bool Framis::alloc_map[psize]={false}; 24 25 void* Framis::operator new(size_t sz) throw(bad_alloc) 26 { 27 for (int i=0; i<psize; ++i) { 28 if (!alloc_map[i]) { 29 out<< "using block " << i << " ... "; 30 alloc_map[i]=true; 31 return pool+(i*sizeof(Framis)); 32 } 33 } 34 out<< "out of memory" <<endl; 35 throw bad_alloc(); 36 } 37 38 void Framis::operator delete(void* m) 39 { 40 if (!m) return; 41 unsigned long block = (unsigned long)m-(unsigned long)pool; 42 block /= sizeof(Framis); 43 out<< "freeing block " << block <<endl; 44 alloc_map[block]=false; 45 } 46 47 int main() 48 { 49 Framis* f[Framis::psize]; 50 try { 51 for (int i=0; i<Framis::psize; i++) { 52 f[i]=new Framis; 53 } 54 new Framis; 55 } catch(bad_alloc) { 56 cerr<< "Out of memory!" <<endl; 57 } 58 delete f[10]; 59 f[10]=0; 60 Framis* X=new Framis; 61 delete X; 62 for (int j=0; j<Framis::psize; j++) { 63 delete f[j]; 64 } 65 return 0; 66 }
上一段文字中我們講到如果為一個類重載operator new()和operator delete(),那麼無論何時創建這個類的一個對象都將調用這些運算符。但是如果要創建這個類的一個對象數組的時候,全局operator new()就會被立即調用,用來為這個數組分配足夠的內存。對此,我們可以通過為這個類重載運算符的數組版本,即operator new[]和operator delete[],來控制對象數組的內存分配。
1 #include<iostream> 2 #include<fstream> 3 #include<new> 4 using namespace std; 5 ofstream trace("ArrayOperatorNew.out"); 6 7 class Widget 8 { 9 public: 10 Widget() {trace<< "*" <<endl; } 11 ~Widget() {trace<< "~" <<endl; } 12 void* operator new(size_t sz) { 13 trace<< "Widget::new: " << sz << " byte" <<endl; 14 return ::new char[sz]; 15 } 16 void operator delete(void* p) { 17 trace<< "Widget::delete" <<endl; 18 ::delete []p; 19 } 20 void* operator new[](size_t sz) { 21 trace<< "Widget::new[]: " << sz << " bytes" <<endl; 22 return ::new char[sz]; 23 } 24 void operator delete[](void* p) { 25 trace<< "Widget::delete[]" <<endl; 26 ::delete []p; 27 } 28 private: 29 enum{sz=10 }; 30 int an[sz]; 31 }; 32 33 int main() 34 { 35 trace<< "new Widget" <<endl; 36 Widget* w=new Widget; 37 trace<<endl<< "delete Widget" <<endl; 38 delete w; 39 trace<<endl<< "new Widget[25]" <<endl; 40 Widget* wa=new Widget[25]; 41 trace<<endl<< "delete []Widget" <<endl; 42 delete []wa; 43 return 0; 44 }
1:對於C++中的動態對象創建又有了新的認識,學習了重載new和delete
2:C++空類的大小是1
3:最讓我激動的就是:C++程序把運行結果寫入新創建的文檔裡面,這個和ACM裡常用的文件讀寫還是不一樣滴。
好吧,繼續努力!!!