摘自《现代C++语言核心特性解析》,谢丙堃 著

委托构造函数是C++11引入的一个新特性,它细化了构造函数的种类,将原来的构造函数细化为委托构造函数(delegating constructor)代理构造函数(target constructor),旨在解决构造函数代码冗余的问题。

冗余的构造函数

一个类为了确保所有成员变量的初始化,有很多构造函数是很常见的。

class X{
public:
X(): a_(0), b_(0.) { CommonInit(); }
X(int a): a_(a), b_(0.) { CommonInit(); }
X(double b): a_(0), b_(b) { CommonInit(); }
X(int a, double b): a_(a), b_(b) { CommonInit(); }
private:
void CommonInit(){}
int a_;
double b_;
}

虽然该代码没有任何语法问题,但构造函数包含了太多的重复代码,使得维护变得非常困难。当成员变量变多时,所需要的成员变量就变得更多了。

如果将初始化部分放到CommonInit函数里,即这样:

class X{
public:
X() { CommonInit(0, 0.); }
X(int a) { CommonInit(a, 0.); }
X(double b) { CommonInit(0, b); }
X(int a, double b) { CommonInit(a, b); }
private:
void CommonInit(int a, double b){
a_ = a;
b_ = b;
}
int a_;
double b_;
}

但实际上这样一方面对性能有一些损失,另一方面也有不可行的可能。

因为在调用CommonInit函数时,变量的初始化已经完毕了(初始化列表进行初始化),在该函数进行赋值,实际上是对变量进行了两次操作(初始化一次,赋值一次)。

另外,赋值依靠的是赋值运算符,如果有的数据成员被禁用了赋值运算符,则会编译错误。

委托构造函数

因此委托构造函数就是在初始化列表中调用其他的构造函数,去委托其他构造函数帮忙初始化。

class X{
public:
X(): X(0, 0.) {}
X(int a): X(a, 0.) {}
X(double b): X(0, b) {}
X(int a, double b): a_(a), b_(b) { CommonInit(); }
private:
void CommonInit(){}
int a_;
double b_;
}

当然,委托构造函数可以多级委托。

当定义一个变量时,其执行过程如下。

X a;

X() -> X(0, 0.) -> CommonInit() -> {} -> 结束

先调用无参构造函数,然后调用X(int, double)构造函数,然后执行该构造函数的主体,最后再执行X()X()的主体。

谨防递归委托

在初始化列表委托构造函数的话,就不能再初始化其他变量,即以下书写时非法的:

X(): X(0), b_(0.) {}

委托模板构造函数

委托模板构造函数就是委托的函数时一个模板函数,一个例子。

class X {
template<class T> X(T first, T last) : l_(first, last) { }
list<int> l_;
public:
X(vector<short>&);
X(deque<int>&);
};

X::X(vector<short>& v) : X(v.begin(), v.end()) { }
X::X(deque<int>& v) : X(v.begin(), v.end()) { }

int main(int argc, char** argv)
{
vector<short> a{ 1,2,3,4,5 };
deque<int> b{ 1,2,3,4,5 };
X x1(a);
X x2(b);
return 0;
}

这样将用vectordeque初始化的构造函数委托给模板构造函数,就无需编写vector<short>deque<int>的代理构造函数,只要保证参数类型支持迭代器即可。

捕获委托构造函数的异常

捕获异常的写法我第一次看非常震惊,直接看例子。

#include <iostream>

class X {
public:
X() try : X(0) {}
catch (int e) {
cout << "catch: " << e << endl;
throw 3;
}
X(int a) try : X(a, 0.) {}
catch (int e) {
cout << "catch: " << e << endl;
throw 2;
}
X(double b) : X(0, b) {}

X(int a, double b) : a_(a), b_(b)
{
throw 1;
}
private:
int a_;
double b_;
};


int main(int argc, char** argv)
{
try {
X x;
}
catch (int e) {
cout << "catch: " << e << endl;
}
return 0;
}

其输出结果为

catch: 1
catch: 2
catch: 3

委托参数较少的构造函数

以上注意到我们委托的构造函数一般都是参数更多函数,实际上也可以委托给参数更少的构造函数,这子类跟调用父类构造函数非常类似,由参数更少的构造函数完成一些初始化函数,自己再做别的事情。

总结

为了解决构造两数冗余的问题,C++委员会想了很多办法,本章介绍的委托构造两数就是其中之一,也是最重要的方法。通过委托构造两数,我们可以有效地減少构造两数重复初始化数据成员的问题,将初始化工作统一地交给某个构造西数来完成。这样在需要增减和修改数据成员的时候就只需要修改代理构造两数即可。不止如此,委托构造两数甚至支持通过模板来进一步简化编写多余构造两数的工作,可以说该特性对于复杂类结构是非常高效且实用的。