本文參考文獻:GeekBand課堂內容,授課老師:侯捷html
:深度探索C++對象模型(侯捷譯)數組
:網絡資料: http://www.leavesite.com/geekband-cpp-5.html網絡
http://blog.csdn.net/wudaijun/article/details/9273339函數
本週的課題是:「 爲上週題目中的 Fruit和Apple 添加 構造函數與 析構函數, 並在構造函數與析構函數中打印控制檯信息,觀察構造和析枸調用過程。而後爲Apple類重載::operator new和 ::operator delete,在控制檯打印信息,並觀察調用結果。」測試
雖然其中構造與析構調用過程上次代碼裏已經實現了,而且比如今這份還要完善一些。但作爲開場白再講一遍比較好!ui
首先,先看下 類的結構。Apple 類繼承自基類Fruitthis
1 //基類 2 class Fruit 3 { 4 public: 5 //使用自帶的構造函數 6 Fruit() 7 { 8 cout << "Call Fruit Constructor.this = " <<this<< endl; 9 } 10 //打印變量內存地址 11 void print(){} 12 //虛函數的影響 13 virtual void process(){} 14 15 virtual ~Fruit() 16 { 17 cout << "Call Fruit Destructor = " << this << endl; 18 } 19 20 private: 21 int no; 22 double weight; 23 char key; 24 }; 25 26 //這裏考慮本身自己的虛函數,及基類的虛函數 27 class Apple : public Fruit 28 { 29 public: 30 //使用默認的構造函數 31 Apple() 32 { 33 cout << "Call Apple Constructor.this = " << this << endl; 34 }; 35 //打印成員數據 36 void save(){} 37 virtual void process(){} 38 virtual ~Apple() 39 { 40 cout << "Call Apple Destructor.this = " << this << endl; 41 } 42 43 //測試2、拋出異常 44 static void* operator new(size_t size); 45 //測試3、沒有拋出異常,此版本須要註釋掉測試二 46 //static void* operator new(size_t size, const std::nothrow_t& nothrow_value); 47 48 //測試4、帶有調試信息的版本,此版本須要註釋掉測試2、測試三 49 //inline void* Apple::operator new(size_t size, const char* file, int line); 50 51 //delete 版本 52 static void operator delete(void* ptr, size_t size) throw(); 53 54 //測試5、測試數組 55 static void *operator new[](size_t size); 56 static void operator delete[](void *ptr); 57 58 private: 59 int size; 60 char type; 61 };
那麼問題來了,Apple 類和Fruit誰先構造、又誰先析構呢?進而思考,基類和子類誰更大一些?spa
衆所周知,子類擁有父類的一切信息,並且子類有些信息更具體,好比鳥都有翅膀,這是共性。可是好比啄木鳥的嘴特別長,這就是特性。天然界是共性與特性的統一。.net
不過從哲學的角度來看,如「人是社會關係的總和」,講的也是這個道理。3d
扯得有點遠了,看圖!因此構造時先構造內部,而後構造外部,析構時正好相反!
能夠充分證實這個觀點,還有問題的話,拷貝我上篇blog代碼,能夠有更詳細的分析,這裏就不展開講了。畢竟只是開場白!
一個類中,若是什麼數據都沒有!打印結果倒是1
class Empty { }; int main(int argc, char** argv) { std::cout << sizeof(Empty) << std::endl; return 0; }
因此咱們爲類進行new 重載時應該也要考慮到這一點。至於爲何是1,不是0,也而不是其餘的數據。我沒弄清楚。但根據調試結果來分析,
咱們在重載應該考慮到這一點。
首先應該判斷下size是否爲0。有指針時也要判斷指針是否爲空。
inline void* Apple::operator new(size_t size) { if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
由葉卡同窗的blog中記錄的 C++ Primer 557所示,成員operator new() 和 operator delete()會自動成爲static成員。
所以,它們沒有this指針,並且也不會修改物件內容,僅僅做爲開闢空間、和清楚空間的做用!
throwing (1) void* operator new (std::size_t size) throw (std::bad_alloc); nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); placement (3) void* operator new (std::size_t size, void* ptr) throw();
第一、2種的區別 是有無拋出異常,其中有拋出異常的還能夠進一步拋出信息,下面將會分析。
第3種 placement new,它也是對operator new的一個重載,定義於<new>中,它多接收一個ptr參數,但它只是簡單地返回ptr。這裏暫時沒有詳細分析,請同窗自行查閱資料。(我上面的推薦資料裏就有)
/* 測試2、拋出異常的版本 */ inline void* Apple::operator new(size_t size) { if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
運行圖以下:
從上圖分析得出,Fruit的Size爲32,Apple 的Size爲40。與上述相對應。
/* 測試3、沒有拋出異常的版本 */ inline void* Apple:: operator new(size_t size, const std::nothrow_t& nothrow_value) { //即便是空類,大小也爲1 if (size == 0) { return malloc(1); } else std::cout << "call Apple::operator new nothrow" << std::endl; return malloc(size); }
這個版本是沒有返回異常信息的版本
如圖所示,New的過程當中那些打印信息並無顯示。
new 這類信息每每會用在調試代碼階段。能比較方便的顯示出行數及文件信息。
* 測試4、拋出異常,並帶有調試信息的版本 此版本使用時,會對以上兩個版本發生衝突,須要註釋掉另外兩個函數,及使用 */ inline void* Apple::operator new(size_t size, const char* file, int line) { //即便是空類,大小也爲1 if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { std::cout << "call A::operator new on file:" << file << " line:" << line << std::endl; cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
在測試頭部也要添加信息
//測試4、打開註釋 //#define new new(__FILE__, __LINE__)
如圖所示,顯示了文件、及行數信息,方便調試。
/* 測試1、棧空間,使用自帶的new 和全局new */ Apple ptrApple; Fruit *ptr = new Fruit(); delete ptr; Apple* ptr1 = new Apple();//Apple 是臨時變量,所佔空間是以new動態分配而得,並由p指向,佔用空間爲堆 delete ptr1;
這裏有兩種方法使用Apple 類,第一種爲棧調用的方法,第二種爲堆調用的方法(本身malloc)。這兩種方法調用new 和delete的位置不一樣。
如圖所示, 這裏實際上有幾個步驟:
一、分配內存.
二、指針類型轉換
三、調用構造函數
分配內存這一操做就是由operator new(size_t)來完成的,若是類A重載了operator new,那麼將調用A::operator new(size_t ),若是沒有重載,就調用::operator new(size_t ),
經過以上結果對比,做用域覆蓋原則,即在裏向外尋找operator new的重載時,只要找到operator new()函數就再也不向外查找,若是參數符合則經過,若是參數不符合則報錯,而無論全局是否還有相匹配的函數原型。
既先查找類中的operator new()和 operator delete(),而後再執行全局operator new()和 operator delete()。
/* 測試5、類中重載new[] 和 delete[] */ inline void* Apple::operator new[](size_t size) { //即便是空類,大小也爲1 if (size == 0) { return malloc(1); } cout << "This is Apple New[]! Now allocating space :" << size << "Byte!" << endl; return malloc(size); } inline void Apple::operator delete[](void *ptr) { if (ptr) { cout << "This is Apple Delete[], Now free space!" << endl; free(ptr); } else { ptr = NULL; } }
Apple *ptr3 = new Apple[3]; cout << "ptr3[0] addr: " << ptr3 << endl; cout << "ptr3[1] addr: " << ptr3 + 1 << endl; cout << "ptr3[2] addr: " << ptr3 + 2 << endl; delete[] ptr3; ptr3 = NULL;
下面用圖來解釋下,(此圖源於某blog內容,後面圖保存了,卻找不到來源,請做者勿怪,若有侵權,請聯繫我,謝謝)
delete的過程
煩請路過的朋友,批評指針。感謝網絡的無私奉獻者。 修改於 2016.08.15 17:28
內容修改中,8月15日晚11:30分前
上傳最新版本