C++11 标准新特性:委派构造函数
陳 晶
2015 年 8 月 11 日發布 ? 1
本文首先介紹了在委派構造函數提出之前類成員構造所面臨的問題,再結合實例介紹了委派構造函數的用法,并說明了使用委派構造函數特性時需要注意的語言點。在本文最后還介紹了 IBM XL C/C++編譯器用來控制該特性的編譯選項。
特性背景
在 C++98 中,如果一個類有多個構造函數且要實現類成員構造,這些構造函數通常要包含基本相同的類成員構造代碼。在最壞的情況下,相同的類成員構造語句被拷貝粘貼在每一個構造函數中,請參考下面的例子:
清單 1.程序源代碼 (ctorini.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 | class A{ public: // 構造函數 A(), A(int i)和 A(int i, int j)有相同的函數體 A(): num1(0), num2(0) {average=(num1+num2)/2;} A(int i): num1(i), num2(0) {average=(num1+num2)/2;} A(int i, int j): num1(i), num2(j) {average=(num1+num2)/2;} private: int num1; int num2; int average; }; |
上例中的三個構造函數有完全相同的函數體。重復的代碼會增大維護的工作量和難度。如果程序員想增加更多的類成員構造語句或者修改已有類成員的類型,他需要在三個構造函數中做三次完全相同的改動。為了避免代碼重復,一些程序員將公有的類成員構造代碼移到一個類成員函數里,意圖讓構造函數通過調用該成員函數完成類成員構造。清單 1 的程序根據這個思路,可以修改如下:
清單 2.程序源代碼 (memfuncini.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 | class A{ public: // 構造函數 A(), A(int i)和 A(int i, int j)意圖通過調用成員函數 int()來完成類成員構造 A(): num1(0), num2(0) {init();} A(int i): num1(i), num2(0) {init();} A(int i, int j): num1(i), num2(j) {init();} private: int num1; int num2; int average; void init(){ average=(num1+num2)/2;}; }; |
清單 2 的程序消除了代碼重復的問題,但又引入了以下的新問題:
- 其它的成員函數可能會不小心調用到 init()函數,這樣會造成不可預期的結果。
- 當編譯器處理成員函數調用的時候,全部的類成員其實都已經了完成構造。也就是說,想在類成員函數里實現類成員構造是行不通的。
委派構造函數的提出和基本用法
基于以上類成員構造所面臨的問題,C++11 標準提出了委派構造函數新特性。利用這個特性,程序員可以將公有的類成員構造代碼集中在某一個構造函數里,這個函數被稱為目標構造函數。其他構造函數通過調用目標構造函數來實現類成員構造,這些構造函數被稱為委派構造函數。在該新特性提出之前,構造函數是不能顯式被調用的,委派構造函數打破了這一限制。讓我們用下面例子來說明該新特性是如何使用的:
清單 3.程序源代碼 (delegatingctor1.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class A{ public: // A(int i)為 A()的委派構造函數 A(): A(0){} // A(int i, int j)為 A(int i)的委派構造函數 A(int i): A(i, 0){} // 委派構造鏈為 A()->A(int i)->A(int i, int j) A(int i, int j) { num1=i; num2=j; average=(num1+num2)/2; } private: int num1; int num2; int average; }; |
可以看到,在構造函數 A()的初始化列表里,程序調用了 A(0), 這就是委派構造函數的語法。 我們稱 A(int i)為 A()的目標構造函數,而 A()為 A(int i)的委派構造函數。同理,A(int i, int j)為 A(int i)的目標構造函數,而 A(int i) 為 A(int i, int j)的委派構造函數。在利用了委派構造函數后,整個程序變得更加的清楚和簡潔。目標構造函數和委派構造函數跟其他普通的構造函數一樣有相同的接口和語法,它們并沒有特殊的處理和標簽。從這個例子還可以看到,一個委派構造函數可以是另一個委派構造函數的目標構造函數,委派構造函數和目標構造函數是相對而言的。目標構造函數是通過重載和類參數推導準則而選定的。
在委派過程中,當目標構造函數函數執行完畢后,委派構造函數繼續執行它自己函數體內的其他語句。請參考以下的例子:
清單 4.程序源代碼 (delegatingctor2.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <iostream> using namespace std; class A{ public: ?A(): A(0){ cout << "In A()" << endl;} A(int i): A(i, 0){cout << "In A(int i)" << endl;} A(int i, int j){ num1=i; num2=j; average=(num1+num2)/2; cout << "In A(int i, int j)" << endl; }? private: int num1; int num2; int average; }; int main(){ A a; return 0; } |
該例子的輸出為:
| 1 2 3 | In A(int i, int j) In A(int i) In A() |
委派構造函數的異常處理
當目標構造函數拋出異常時,該異常會被委派構造函數中的 try 模塊抓取到。并且在這種情況下,委派構造函數自己函數體內的代碼就不會被執行了。
在下面的例子中,構造函數 A(int i, int j)拋出一個異常,該異常依次被委派構造函數 A(int i)和 A()抓取到,且 A(int i)和 A()的函數體沒有被執行。
清單 5.程序源代碼 (exception1.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #include <iostream> using namespace std; class A{ public: A(); A(int i); A(int i, int j); private: int num1; int num2; int average; }; A:: A()try: A(0) { // A()函數體不會被執行到 cout << "A() body"<< endl; } catch(...) { cout << "A() catch"<< endl; } A::A(int i) try : A(i, 0){ // A(int i)函數體不會被執行到 cout << "A(int i) body"<< endl; } catch(...) { cout << "A(int i) catch"<< endl; } A::A(int i, int j) try { num1=i; num2=j; average=(num1+num2)/2; cout << "A(int i, int j) body"<< endl;? // 拋出異常 ?throw 1; } catch(...) { cout << "A(int i, int j) catch"<< endl; } int main(){ try{ A a; cout << "main body"<< endl; } catch(...){ cout << "main catch"<< endl; } return 0; } |
該例的輸出為:
| 1 2 3 4 5 | A(int i, int j) body A(int i, int j) catch A(int i) catch A() catch main catch |
當委派構造函數拋出異常時,系統會自動調用目標構造函數內已經構造完成的對象的析構函數。在下面的例子中,目標構造函數 A(int i, int j)完成了對象 a 的構造。它的委派構造函數 A(int i)在執行時拋出了一個異常,此時對象 a 馬上被析構,且 A(int i)的委派構造函數 A()的函數體不再被編譯器執行,這和上例中所描述的原則是一致的。
清單 6.程序源代碼 (exception2.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <iostream> using namespace std; class A{ public: A(); A(int i); A(int i, int j); ~A(); private: int num1; int num2; int average; }; A::A(): A(0){ // A()函數體不會被執行到 cout << "A()body" << endl; } A::A(int i) try : A(i, 0){ cout << "A(int i) body"<< endl; // 拋出異常,對象 a 將被析構 throw 1; } catch(...) { cout << "A(int i) catch"<< endl; } A::A(int i, int j){ num1=i; num2=j; average=(num1+num2)/2; cout << "A(int i, int j) body"<< endl; } A::~A(){ cout << "~A() body"<< endl; } int main(){ A a; return 0; } |
該例的輸出為:
| 1 2 3 4 | A(int i, int j) body A(int i) body ~A() body A(int i) catch |
委派構造函數和泛型編程
綜上所述,委派構造函數可以使程序員規避構造函數里重復的代碼。委派構造函數還有一個很實際的應用:它使得構造函數的泛型編程變得更加容易,請參考以下的例子:
清單 7.程序源代碼 (generic.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <iostream> using namespace std; template<typename T> class A{ public: A(int i): A(i, 0){} A(double d): A(d, 0.0){} // 函數模板 A(T i, T j) { num1=i; num2=j; average=(num1+num2)/2; cout << "average=" << average << endl; } private: T num1; T num2; T average; }; int main(){ A<int> a_int(1); A<double> a_double(1.0); } |
該例的輸出為:
| 1 2 | average=0 average=0.5 |
在這個例子中,目標構造函數為函數模板,它在被委派構造函數調用的時候才被實例化。這樣的用法十分方便,程序員不需要再書寫不同類型的目標構造函數了。
委派構造函數的使用限制
委派構造函數特性簡單好用,非常利于新手的學習和掌握,但是在這個特性的使用中也需要注意以下的限制。
如清單 3 中的程序所示, 一個類中的構造函數可以形成一個委派鏈。但是程序員應該避免委派環的出現。在下面的例子中,構造函數 A(), A(int i), 和 A(int i, int j)形成了一個委派環,這樣的程序是有語法錯誤的。
清單 8.程序源代碼 (delegatingcircle.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 | class A{ public: //形成了委派構造環 A()->A(int i)->A(int i, int j)->A() A(): A(0){} A(int i): A(i, 0){} ?A(int i, int j): A(){} private: int num1; int num2; int average; }; |
委派構造函數不能在初始化列表中包含成員變量的初始化.也就是說,一個構造函數不能在初始化列表中既初始化成員變量,又委派其他構造函數完成構造。在下面的例子中,A()是 A(int i)的委派構造函數,并且在同時又初始化了類成員變量 average。這樣的程序是不被允許的。
清單 9.程序源代碼 (delegatingini.cpp)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class A{ public: //錯誤,A()不能同時委派和初始化類成員 A(): A(0), average(0){} A(int i): A(i, 0){} A(int i, int j) { num1=i; num2=j; average=(num1+num2)/2; } private: int num1; int num2; int average; }; |
編譯選項
IBM XL C/C++編譯器已經實現了委派構造函數這個新特性。在默認情況下,該特性是關閉的,如果程序員需要打開該特性,可以設置下面任意一個編譯選項:
- -qlanglvl=[no]delegatingctors
默認值: -qlanglvl=nodelegatingctors
用法: -qlanglvl=delegatingctors 打開委派構造函數特性,
-qlanglvl=nodelegatingctors 關閉委派構造函數特性。
- -qlanglvl=extended0x
該編譯選項控制所有的 C++11 特性,當程序員設置了該編譯選項時,所有的 IBM XL C/C++編譯器實現的 C++11 特性都會被打開,包括委派構造函數特性。
除了 IBM XL C/C++編譯器,其他很多編譯器也實現了該特性,比如 clang,GNU C++編譯器等。
結束語
本文詳細的介紹了 C++11 標準新特性-委派構造函數。利用該特性,可以提高程序的可讀性和可維護性,減少程序員書寫構造函數的時間。通過委派其他構造函數,多構造函數的類編寫將更加容易。但委派構造函數的調用是需要系統開銷的,如果可以用帶默認參數的構造函數實現和委派構造函數相同的功能,在不影響程序可讀性和可維護性的前提下,更推薦程序員使用前者。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的C++11 标准新特性:委派构造函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的世界链甲怎么获得
- 下一篇: 数据结构学习(二)——单链表的操作之头插