C++11 委派构造函数
【1】为什么需要委派构造函数?
委派构造函数,委派即委托安排,即委托安排其他构造函数代办构造对象的任务。
为什么需要委派构造函数?请先看如下代码片段(示例1):
1 class Info 2 { 3 public: 4 Info() : type(1), name('a') 5 { 6 InitRest(); 7 } 8 Info(int i) : type(i), name('a') 9 { 10 InitRest(); 11 } 12 Info(char e) : type(1), name(e) 13 { 14 InitRest(); 15 } 16 private: 17 void InitRest() 18 { 19 /* 其他初始化 */ 20 } 21 22 int type; 23 char name; 24 // ... 25 };
这段代码是我们经常看到的,难道你不觉得重复代码太多了吗?
好吧~ 如果你没有意识到这个问题,说明你还不是一个合格的程序员。
因为据听说,合格的程序员都是懒汉,不愿意多敲一行代码,能少则少,能懒更懒。
不扯淡了!言归正传,试想上例中能不能在一些构造函数中连InitRest都不用调用呢?答案是肯定的。
但前提是需要将一个构造函数设定为“基准版本”,比如本例中Info()版本的构造函数,而其他构造函数可以通过委派“基准版本”来进行初始化。
按照这个想法,我们改编为如下形式的类构造函数写法(示例2):
1 class Info 2 { 3 public: 4 Info() : Info(1, 'a') { } 5 Info(int i) : Info(i, 'a') { } 6 Info(char e) : Info(1, e) { } 7 private: 8 Info(int i, char e) : type(i), name(e) 9 { 10 /* 其他初始化 */ 11 } 12 int type; 13 char name; 14 // ... 15 };
看明白了吗?来玩找茬,找到区别了吗?先说服不服?心服还是口服?
C++这么牛逼的语言,怎么能不进行革新改进呢?那还岂能在编程语言界保持独领风骚。
为了实现第二种写法,就是引入委派构造函数的原因。
【2】委派构造函数应用注意项
为了区分被调用者和调用者,称在初始化列表中调用“基准版本”的构造函数为委派构造函数(delegating constructor),而被调用的“基准版本”则为目标构造函数。
在C++11中,所谓委派构造,就是指委派函数将构造的任务委派给了目标构造函数来完成这样一种类构造的方式。
(1)C++11中的委派构造函数是在构造函数的初始化列表位置进行构造的、委派的。
(2)构造函数不能同时“委派”和使用初始化列表。
(3)目标构造函数的执行总是先于委派构造函数。因此避免目标构造函数和委托构造函数体中初始化同样的成员通常是必要的,否则则可能发生代码错误。
请看如下代码错误示例:
1 class InfoA 2 { 3 public: 4 InfoA() : InfoA(1, 'a') { } 5 InfoA(int i) : InfoA(i, 'a') { } 6 InfoA(char e) : InfoA(1, e) { } 7 private: 8 InfoA(int i, char e) : type(i), name(e) 9 { 10 type += 1; 11 } 12 int type; 13 char name; 14 // ... 15 }; 16 17 class InfoB 18 { 19 public: 20 InfoB() { InitRest(); } 21 InfoB(int i) : InfoB() { type = i; } 22 InfoB(char e) : InfoB() { name = e; } 23 private: 24 void InitRest() 25 { 26 type += 1; 27 } 28 29 int type{ 1 }; 30 char name{ 'a' }; 31 // ... 32 }; 33 34 int main() 35 { 36 InfoA objA(3); 37 InfoB objB(3); 38 39 return 0; 40 }
运行结果如下图(自行分析为什么是这样的结果):
综上所以,建议程序员抽象出最为“通用”的行为做目标构造函数。这样做一来代码清晰,二来行为也更加正确。
(4)避免委托环:在委托构造的链状关系中不能形成委托环(delegation cycle)。
首先,什么是委托构造的链状关系呢?
即可能会拥有不止一个委派构造函数,而且一些目标构造函数很可能也是委派构造函数,这样的情况就可能在委派构造函数中形成链状的委派构造关系。
如下示例:
1 class Info 2 { 3 public: 4 Info() : Info(1) { } // 委派构造函数 5 Info(int i) : Info(i, 'a') { } // 既是目标构造函数,也是委派构造函数 6 Info(char e) : Info(1, e) { } 7 private: 8 Info(int i, char e) : type(i), name(e) 9 { 10 /* 其他 初始化 */ 11 } // 目标构造函数 12 13 int type; 14 char name; 15 };
理解了委托构造的链状关系,那么委托环就很想象了。请看如下示例:
1 struct DelegatCycle 2 { 3 int i, c; 4 DelegatCycle() : DelegatCycle(2) {} 5 DelegatCycle(int i) : DelegatCycle('c') {} 6 DelegatCycle(char c) : DelegatCycle(2) {} 7 };
仔细找找“环”在哪里,不做赘述。
(5)构造模板函数:委派构造的一个很实际的应用就是使用构造模板函数产生目标构造函数。
如下示例:
1 #include <list> 2 #include <vector> 3 #include <deque> 4 using namespace std; 5 6 class TDConstructed 7 { 8 template<class T> TDConstructed(T first, T last) : l( first, last) 9 {} 10 list< int> l; 11 12 public: 13 TDConstructed(vector<short> & v) : TDConstructed(v.begin(), v.end()) 14 {} 15 TDConstructed(deque<int> & d): TDConstructed(d.begin(), d.end()) 16 {} 17 };
定义了一个构造函数模板。而通过两个委派构造函数的委托,构造函数模板会被实例化。
T会分别被推导为vector<short>::iterator和deque<int>::iterator两种类型。
这样一来,TDConstructed类就可以很容易地接受多种容器对其进行初始化。
(6)异常处理:如果在委派构造函数中使用try的话,那么从目标构造函数中产生的异常,都可以在委派构造函数中被捕捉到。
如下示例:
1 #include <iostream> 2 using namespace std; 3 4 class DCExcept 5 { 6 public: 7 DCExcept(double d) try : DCExcept(1, d) 8 { 9 cout << "Run the body." << endl; 10 // 其他 初始化 11 } catch (...) 12 { 13 cout << "caught exception." << endl; 14 } 15 private: 16 DCExcept(int i, double d) 17 { 18 cout << "going to throw!" << endl; 19 throw 0; 20 } 21 22 int type; 23 double data; 24 }; 25 26 int main() 27 { 28 DCExcept a(12.34); 29 } 30 31 /* 32 going to throw! 33 caught exception. 34 */
在目标构造函数DCExcept(int,double)抛出了一个异常,并在委派构造函数DCExcept(int)中进行捕捉。
编译运行该程序,从运行结果可以看到,由于在目标构造函数中抛出了异常,委派构造函数的函数体部分的代码并没有被执行。
goog good study, day day up.
顺序 选择 循环 总结