本篇筆記主要分為兩個主要部分,第一部分關於對象模型,第二部分是關於new和delete的更加深入的學習。
一、對象模型
-
關於vptr(虛指針)和vtbl(虛函數表)
只要用到了虛函數,對象中就會多一個指向虛函數表的虛指針。在32位環境下,將占4Bytes的空間。
在vtbl中,每一項都是指向自己類應當調用的虛函數的函數指針。


這里提一下,如果父類定義成虛函數,子類中和父類虛函數相同名稱,參數表相同的函數會自動變成虛函數。不管加沒有virtual關鍵字。通常我們還是要加上關鍵字來確保代碼可讀性。
2.靜態綁定與動態綁定
在C中,對於不同的函數名采用靜態綁定的方法,每個函數直接對應了一個地址,存儲在相應的位置中。在C++中,非虛的成員函數也用靜態綁定的方式被存儲。如上圖中的A::func1()等成員函數。
不過對於虛函數,C++中采用了動態綁定的方法。在上圖中,每個虛函數都存儲在虛函數表中。當調用虛函數時,編譯器會隨着上圖中的路徑找到正確的函數調用。
由於動態綁定,不管什么地方調用虛函數,總能得到正確的結果這個機制限制了虛函數應該被虛函數覆蓋。
虛函數可以由以下的兩種方式得到。

一個非常常用的基於虛函數的方式是建立一個指向抽象類的指針鏈表,這是多態的體現。

關於類的對象模型的內存分配,涉及到對象的位對齊的規則,在博客中的另一篇文章中有簡單介紹。
二、再談new和delete
-
定位new和delete運算符及其重載
new和delete在使用時可以有一個可選的指針類型的參數,用來指定內存分配的起始地址。如果沒有這一參數,則會在堆空間中自動分配一段合適大小的空間。
默認的一個定位new函數是:
void* operator new(size_t size,void *start)
在使用時可以采用例如如下的方法:int *p = new(0x12345678) int;
事實上,我們還可以使用別的參數列進行new操作,我們也可以對operator new和operator delete進行重載。例如下面的兩個常用的參數列:
void* operator new(size_t size,long extra)//extra參數用於多申請一段存儲空間,專門用來存儲一些特別的信息,例如引用計數的信息。
void* operator new(size_t size,long extra,char init)
new和delete操作固有的定義是:
inline void * operator new(size_t size){return malloc(size);}
inline void * operator new[](size_t size){return malloc(size);}//這里在調用時size會自動進行計算。
inline void operator delete(void * ptr){free(ptr);}
inline void operator delete[](void * ptr){free(ptr);}
注意,如果需要進行重載,new第一個參數始終應該是size_t格式的,delete的第一個參數始終是void*類型。此外,通常情況下有參數的delete不會被調用,而是繼續采用無參的方法,直接釋放內存。
此外,new和delete也可以作為類的成員函數重載,這樣重載出來的操作將僅用於這個類。需要注意的是,new和delete操作應該是靜態的,其原因在於:
1.創建對象時,先分配內存,此時沒有對象,只能是靜態的。
2.刪除對象,先執行析構,析構后已經沒有對象了,所以這里也只能是靜態的。
只有當構造函數失敗時,才會去尋找匹配的operator delete函數去釋放空間;如果沒有定義相應的delete函數,就代表放棄處理創建失敗的這一異常。
注:
當一對operator new 和operator delete除了第一個參數之外,剩下的參數都一致時,稱這兩個操作"匹配"。
從new操作顯式拋出異常,並不會觸發特殊的delete。從new操作中拋出異常,代表內存分配沒有進行,因此也就不需要釋放內存;只有再分配內存之后,構造時產生異常才會觸發特殊的delete。
下面是關於new、delete操作的測試代碼:
1 #ifndef HEADER_H_INCLUDED 2 #define HEADER_H_INCLUDED 3 #include <iostream> 4 #include <stdlib.h> 5 using namespace std; 6 class Fruit{ 7 int no; 8 double weight; 9 char key; 10 public: 11 void print() { cout<<"no =\t"<<no<<"\tweight =\t"<<weight<<"\tkey =\t"<<key<<endl; } 12 virtual void process(){ } 13 Fruit(int n = 0,double w = 0,char k = 0):no(n),weight(w),key(k){ cout<<"Ctor of Fruit is called.\nno =\t"<<no<<"\tweight =\t"<<weight<<"\tkey =\t"<<key<<endl;} 14 virtual ~Fruit(){cout<<"Dtor of Fruit is called.\nno =\t"<<no<<endl;} 15 }; 16 17 class Apple: public Fruit{ 18 int size; 19 char type; 20 public: 21 void save() { } 22 virtual void process(){ } 23 Apple(int s = 0,char t = 0,int n = 0,double w = 0,char k = 0):Fruit(n,w,k),size(s),type(t){ 24 cout<<"Ctor of Apple is called.\n"; 25 print(); 26 cout<< "size =\t"<<size<<"\ttype = \t"<<type<<endl; 27 //注:下面這條語句是為了測試重載版本的delete而特別添加的. 28 if(s<0) throw 1; 29 } 30 virtual ~Apple(){ 31 cout<<"Dtor of Apple is called."<<endl; 32 } 33 static void * operator new(size_t size); 34 static void * operator new(size_t size,long extra); 35 static void operator delete(void * ptr); 36 static void operator delete(void* ptr,long extra); 37 }; 38 void* operator new(size_t size) 39 { 40 cout<<"::operatornew(size_t) is called.\n"; 41 return malloc(size); 42 } 43 void operator delete(void* ptr) 44 { 45 cout<<"::operatordelete(void*) is called.\n"; 46 free(ptr); 47 } 48 49 50 #endif // HEADER_H_INCLUDED
1 #include "header.h" 2 3 using namespace std; 4 5 void* Apple::operator new(size_t size) 6 { 7 cout<<"void* Apple::operator new(size_t) is called.\n"; 8 return malloc(size); 9 } 10 11 void* Apple::operator new(size_t size,long extra) 12 { 13 cout<<"void* Apple::operator new(size_t,long) is called.\n"; 14 return malloc(size+extra); 15 } 16 17 18 void Apple::operator delete(void* ptr) 19 { 20 cout<<"void Apple::operator delete(void*) is called.\n"; 21 free(ptr); 22 } 23 void Apple::operator delete(void* ptr,long extra) 24 { 25 cout<<"void Apple::operator delete(void*,long) is called.\n"; 26 free(ptr); 27 } 28 29 int main() 30 { 31 cout<<"======Fruit new,delete test=======\n"; 32 //這部分的結果體現了: 33 //1.在沒有為類指定單獨的new和delete時,會調用全局的new和delete; 34 //2.造函數先分配內存,再進行構造的特點和析構函數先進行析構,再釋放內存的特點; 35 Fruit * _Fruit = new Fruit(0,0,'A'); 36 delete _Fruit; 37 cout<<"======Apple new,delete test======\n"; 38 //這部分的結果體現了: 39 //1.繼承關系先構造基類,再構造自身 40 //2.析構時先析構自身,再析構基類 41 //3.類里邊有單獨的new和delete時,使用類內定義的new和delete。 42 Apple * _Apple = new Apple(0,0,1,0,'B'); 43 delete _Apple; 44 cout<<"===Apple multi-parameters new,delete test===\n"; 45 //這部分的結果體現了: 46 //1.顯式delete操作總是調用delete(void*),而不管是如何new出來的 47 _Apple = new(long(100)) Apple(0,0,2,0,'C'); 48 delete _Apple; 49 cout<<"=Apple new,delete failed test=\n"; 50 //這部分的結果體現了: 51 //1.當ctor失敗時,會去尋找匹配的dtor. 52 Apple * _Apple_2; 53 try{ 54 _Apple_2 = new(long(100)) Apple(-1); 55 } 56 catch(...) 57 { 58 cout<<"Catch Error."; 59 } 60 return 0; 61 }
