C++的動態對象創建
當創建一個C++對象時,會發生兩件事:
(1)為對象分配內存
(2)調用構造函數來初始化那個內存
然而,為對象分配內存可以用以下幾種方式或在可選擇的時間發生:
(1)在靜態存儲區域,存儲空間在程序開始之前就可以分配。這個存儲空間在整個運行期間都存在。
(2)無論何時到達一個特殊的執行點(左大括號)時,存儲單元都可以在棧上被創建。出了執行點(右大括號),這個存儲單元自動被釋放。這些棧分配運算內置於處理器的指令集中,非常有效。但是,在寫程序的時候,必須知道需要多少個存儲單元,以便編譯器知道生成正確的指令。
(3)存儲單元也可以從一塊稱為堆的地方分配。這被稱為動態內存分配。在運行時調用程序分配這些內存。這就意味著在任何時候可以分配內存以及分配多少內存,當然也需要負責決定何時釋放內存。
為了在運行時動態分配內存,C在它的標准庫函數中提供了一些函數:從堆中申請內存的函數malloc()以及它的變種calloc()和realloc()、釋放內存返回給堆的函數free()。下面我們看一個例子:
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 class Obj 11 { 12 public: 13 void initialize() { 14 cout<< "initializing Obj" <<endl; 15 i = j = k = 0 ; 16 memset(buf, 0, sz); 17 } 18 void destroy() const { 19 cout<< "destroying Obj" <<endl; 20 } 21 private: 22 int i, j, k; 23 enum {sz=100}; 24 char buf[sz]; 25 }; 26 27 int main() 28 { 29 Obj* obj = (Obj*)malloc(sizeof(Obj)); 30 obj->initialize(); 31 obj->destroy(); 32 free(obj); 33 return 0; 34 }
程序中有這樣一行代碼,使用了malloc()為對象分配內存:
1 Obj* obj = (Obj*)malloc(sizeof(Obj));
這裡用戶必須決定對象的長度,由於malloc()只是分配了一塊內存而不是生成一個對象,所以它返回了一個void*類型指針。而C++裡面不允許將一個void*指針賦予任何其他指針,所以必須做類型轉換。
用戶在使用對象之前必須記得對它初始化。注意構造函數沒有被使用,這是因為構造函數不能被顯示地調用---它是在對象創建時由編譯器調用。
許多程序設計者發現C的動態內存分配函數太復雜,容易令人混淆。所以,C程序設計者常常在靜態內存區域使用虛擬內存機制分配很大的變量數組以避免使用動態內存分配。為了在C++中使得一般的程序員可以安全使用庫函數而不費力,所以沒有接受C的動態內存分配方法。
C++中的解決方法是把創建一個對象所需的所有動作都結合在一個稱為new的運算符裡。我們當用new(new 的表達式)創建一個對象時,它就在堆裡為對象分配內存並為這塊內存調用構造函數。我們可以為類使用任何可用的構造函數而寫一個new表達式,如果構造函數沒有參數,可以寫沒有構造函數參數表的new表達式。
new表達式的反面是delete表達式。delete表達式首先調用析構函數,然後釋放內存。如果正在刪除的對象的指針是0,將不發生任何事情。為此,我們經常建議在刪除指針後立即把指針賦值為0以免對它刪除兩次,從而產生某些問題。
1 #ifndef TREE_H 2 #define TREE_H 3 #include<iostream>//Tree.h 4 using namespace std; 5 6 class Tree 7 { 8 public: 9 Tree(int treeHeight) :height(treeHeight) {} 10 ~Tree() {cout<< "*" <<endl; } 11 friend ostream& operator << (ostream& os, const Tree* t) { 12 return os<< "Tree height is: " << t->height <<endl; 13 } 14 private: 15 int height; 16 }; 17 18 #endif // TREE_H
1 #include "Tree.h" 2 using namespace std; 3 4 int main() 5 { 6 Tree* t = new Tree(40); 7 cout<< t; 8 delete t; 9 return 0; 10 }
當使用new在堆上創建對象數組時,如下面這一行代碼:
MyType* fp = new MyType[100];
這樣在堆上為100個MyType對象分配了足夠的內存並為每一個對象調用了構造函數。銷毀這個數組時我們應該這樣寫:
delete []fp;
空的方括號告訴編譯器產生代碼,該代碼的任務是將從數組創建時存放在某處的對象數量取回,並為數組的所有對象調用析構函數。
當operator new()找不到足夠大的連續內存塊來安排對象時,將會發生什麼事情呢?一個稱為new-handler的特殊函數將會被調用。首先,檢查指向函數的指針,如果指針非0,那麼它指向的函數將被調用。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<cmath> 6 #include<algorithm> 7 #include<new> 8 #define inf 0x7fffffff 9 using namespace std; 10 11 int cnt = 0; 12 13 void out_of_memory() 14 { 15 cerr<< "memory exhausted after " << cout << " allocations!" <<endl; 16 exit(1); 17 } 18 19 int main() 20 { 21 set_new_handler(out_of_memory); 22 while (1) { 23 cnt ++ ; 24 new int[1000]; 25 } 26 return 0; 27 }
說明:new-handler函數必須不帶參數且其返回值為void 。While循環將持續分配int對象直到空的內存被耗盡。在緊接下去的下一次對new的調用時,將沒有內存可被調用,所有調用了new-handler函數。
new-handler函數試著調用operator new(),如果已經重載了operator new(),則new-handler將不會按默認調用。所以如果我們仍想調用new-handler,那麼我們不得不在重載的operator new()的代碼裡加上做這些工作的代碼。
下一篇的動態對象創建將會給大家講解重載new和delete。