27,二階構造模式(建構函式二階構造)
模式------------即方法
1.建構函式的回顧
(1)關於建構函式
①類的建構函式用於物件的初始化
②建構函式與類同名並且沒有返回值(思考:無返回值如何判斷建構函式的執行結果?)
③建構函式在物件定義時自動被呼叫
問題:
1,如何判斷建構函式的執行結果? ---普通函式可以返回值
2,建構函式中執行return會發生什麼?------普通函式立即返回,後面的函式不執行
3,建構函式執行結束是否意味著物件構造成功?-------
程式設計實驗以上問題:
1 #include<stdio.h> 2 /* 1,沒有辦法判斷建構函式的執行結果 3 2,建構函式中執行return會發生什麼?建構函式執行之後,不意味著物件構造成功4 */ 5 // 模式即方法 6 class Test 7 { 8 int mi; 9 int mj; 10 11 public: 12 13 Test(int i, int j) //1,建構函式中執行return會發生什麼? 14 { //答:直接返回,Test t1(1,2)有問題,初始狀態有問題,沒有影響建構函式的誕生,不能知道t1能不能用 15 //所以沒有辦法判斷建構函式的執行結果 16 mi = i; 17 18return; 19 20 mj = j; //這行程式碼沒有執行--------mj變為隨機數,mj成員未完成初始化 21 } 22 23 int getI() 24 { 25 return mi; 26 } 27 28 int getJ() 29 { 30 return mj; 31 } 32 }; 33 34 int main() 35 { 36 Test t1(1,2); 37 38 printf("t1.mi=%d\n",t1.getI()); //1 39 printf("t1.mj=%d\n", t1.getJ()); //5689365 40 41 return 0; 42 }
回答:建構函式的真相
①建構函式只提供自動初始化成員變數的機會,但不能保證初始化邏輯一定成功。它只能決定物件的初始狀態,而不是物件的誕生!
②建構函式中執行return語句後,建構函式執行結束,但是不意味著物件構造成功。
那麼怎麼解決?????
1 #include<stdio.h> 2 /* 3 判斷物件構造是否成功------方案:類中增加成員-mStatus-----強行讓建構函式有個返回值mStatus 4 */ 5 // 模式即方法 6 class Test 7 { 8 int mi; 9 int mj; 10 11 bool mStatus; 12 13 public: 14 Test(int i, int j) : mStatus(false) //類的成員變數mStatus的初始化 15 { 16 mi = i; 17 18 return; //mj變為隨機數,mj成員未完成初始化 19 20 mj = j; 21 22 mStatus = true; //物件構造成功,mStatus返回true 23 } 24 int getI() 25 { 26 return mi; 27 } 28 int getJ() 29 { 30 return mj; 31 } 32 33 int status() //功能函式 34 { 35 return mStatus; 36 } 37 }; 38 39 int main() 40 { 41 Test t1(1, 2); 42 43 if (t1.status())//構造未完成,status為false 44 { 45 printf("t1.mi=%d\n", t1.getI());//i被正確初始化 46 printf("t1.mj=%d\n", t1.getJ());//j為隨機值 47 } 48 return 0; 49 }
2.半成品物件
(1)初始化操作不能按照預期完成而得到的物件
(2)半成品物件是合法的C++物件,也是bug的重要來源
記憶體申請不成功,產生段錯誤,但是有的錯誤只產生一次,該怎麼解決這種問題???
人為的將構造分為兩步
3.二階構造---(用於杜絕半成品物件)
(1)工程開發中的構造過程
①資源無關的初始化操作階段:
----------不會出現異常情況的操作(比如將成員變數的值設定為初始值)
②需要使用系統資源的操作
-------可能出現異常,如記憶體申請(堆空間),訪問檔案等。
二階構造,物件只在堆空間產生,不能在棧上產生,往往實際工程物件是非常大的,不適合放在棧空間
為什麼要用物件建立函式???
因為建構函式是Private外界不能呼叫,所以要通過物件建立函式(static)
例項:
1 #include<stdio.h> 2 3 4 5 class TwoPhaseCons 6 { 7 private: 8 TwoPhaseCons() //第一階段構造函(簡單的賦值操作) c++建構函式可以擔任二階構造的一階建構函式 9 { 10 } 11 bool construct() //第二階段建構函式(系統資源申請,開啟檔案,網路) 普通成員函式 12 { 13 return true; 14 } 15 public: 16 //建構函式時Private外界不能呼叫,通過物件建立函式 17 static TwoPhaseCons* NewInstance(); //物件建立函式 -----------返回物件指標 18 19 }; 20 21 //物件建立函式實現 22 TwoPhaseCons* TwoPhaseCons::NewInstance() 23 { 24 TwoPhaseCons* ret = new TwoPhaseCons(); //堆空間建立物件,呼叫建構函式----執行第一階段構造--(靜態成員函式內部可以訪問類的私有成員) 25 26 27 //r若第二階段構造失敗,返回NULL 28 if (!(ret && ret->construct())) //construct----進行資源申請-------執行二階構造 29 { 30 delete ret; //刪除半成品物件 31 ret = NULL; 32 } 33 34 return ret; //返回物件,資源申請成功-----合法物件NewInstance()申請成功 35 } 36 37 int main() 38 { 39 // TwoPhaseCons obj; //error 40 41 // TwoPhaseCons* obj =new NewInstance(); //error 觸發建構函式自動呼叫,但是建構函式是私有的,不能在外部main()自動呼叫,只能呼叫建立函式 42 43 TwoPhaseCons* obj= TwoPhaseCons::NewInstance(); //只能呼叫靜態建立函式 44 45 printf("obj=%p\n",obj); //可以得到合法可用的物件----位於堆空間 46 47 return 0; 48 }
【程式設計實驗】陣列類的加強 IntArray
//IntArray.h
1 #ifndef _INTARRAY_H 2 #define _INTARRAY_H 3 4 class IntArray 5 { 6 private: 7 int m_length; //陣列長度 8 int* m_pointer; //陣列資料 9 10 IntArray(int len); //一階構造 11 IntArray(const IntArray& obj); //一階構造 12 bool construct(); //實現第二階段的構造 13 14 public: 15 static IntArray* NewInstance(int length); //物件建立函式 16 17 int length(); //獲取陣列長度 18 bool get(int index, int& value); //獲取對應位置的陣列元素值 19 bool set(int index, int value); //設定對應位置的陣列元素為引數value 20 void free(); 21 }; 22 23 #endif 24
////IntArray.cpp
1 #include"IntArray.h" 2 3 //第一階段存放不存在錯誤的操作 4 //可能失敗的操作放在第二階段 5 6 IntArray :: IntArray(int len) //作用域分辨符:: 7 { 8 m_length = len; 9 } 10 11 bool IntArray::construct() //建構函式 12 { 13 bool ret = true; 14 15 m_pointer = new int[m_length]; //資料指標指向堆空間裡的一段記憶體:陣列作用,儲存整形陣列 16 17 if (m_pointer) // 要判斷記憶體是否申請成功 18 { 19 for (int i = 0; i < m_length; i++) //將陣列所有值設定為0 20 { 21 m_pointer[i] = 0; 22 } 23 } 24 else 25 { 26 ret = false; 27 } 28 return ret; 29 } 30 31 int IntArray :: length() 32 { 33 return m_length; 34 } 35 36 bool IntArray :: get(int index, int& value) //安全性體現----得到對應位置的陣列元素值 ---index為指定位置 37 { 38 bool ret = (0 <= index) && (index <= length()); 39 40 if (ret) 41 { 42 value = m_pointer[index]; //通過引用返回值 43 } 44 45 return ret; 46 } 47 48 IntArray* IntArray::NewInstance(int length) ///物件建立函式 49 { 50 IntArray* ret = new IntArray(length); 51 52 //r若第二階段構造失敗,返回NULL 53 if (!(ret && ret->construct())) 54 { 55 delete ret; 56 ret = 0; 57 } 58 59 return ret; 60 } 61 62 63 bool IntArray :: set(int index, int value) //設定對應位置的陣列元素為引數value 64 { 65 bool ret = (0 <= index) && (index <= length()); 66 if (ret) 67 { 68 m_pointer[index]=value; //陣列成員重新賦值 69 } 70 return ret; 71 } 72 73 void IntArray::free() 74 { 75 delete[] m_pointer; //釋放堆空間 76 }
//main.c
1 #include<stdio.h> 2 #include "IntArray.h" 3 4 5 int main() { 6 7 8 //呼叫建立函式 9 IntArray* a = IntArray::NewInstance(5); 10 printf("a.length=%d\n",a->length()); 11 12 a->set(0, 1); 13 14 for (int i = 0; i <a->length() ; i++) 15 { 16 int v = 0; 17 18 a->get(i, v); 19 20 printf("a[%d]=%d\n",i,v); 21 } 22 23 24 return 0; 25 }
4.小結
(1)建構函式只能決定物件的初始化狀態
(2)建構函式中初始化操作的失敗不影響物件的誕生
(3)初始化不完全的半成品物件是bug的重要來源
(4)二階構造人為的將初始化過程分為兩個部分
(5)二階構造能夠確保建立的物件都是完整初始化的