一.目的
委派构造函数是C++ 11中对C++的构造函数的一项改进,其目的 也是为了减少程序员书写构造函数的时间。通过委派其他构造函数, 多构造函数的类编写将更加容易。
二.委派构造函数的前因后果
class Info
{
public:
Info() : type(1), name('a'){initRest();}
Info(int i) : type(i), name('a'){initRest();}
Info(char e) : type(1), name(e){initRest()}
~Info(){}
private:
void initRest(){}
int type;
char name;
};
在类Info中,一共存在三个构造函数,并且三个构造函数中都调用了initRest()函数,这样的代码存在很大的冗余性。
下面是使用C++11的快速初始化成员变量的新特性来改写上面这段代码:
class Info()
{
public:
Info(){initRest();}
Info(int i) : type(i)(initRest();)
Info(char e) : name (e){initRest();}
private:
initRest();
int type {1};
char name {'e'};
};
上面的代码虽然经过C++ 11的快速初始化成员变量的方式改造了,但是仍然存在一个问题,就是每个构造函数都调用了initRest函数,代码的冗余度还是很大。
下面这一版是一种存在错误的改进方式
class Info
{
public:
Info(){initRest();}
Info(int i){this->Info(); this->type = i;}
Info(char e){this->Info(); this->name = e;}
~Info(){}
private:
int type;
char name;
initRest(){}
};
上面的代码编译器是编译不过的,即使这段代码能编译过,它也不是最优的。当然,我们还可以开发出一个更具有“ 黑客 精神” 的版本:
class Info
{
public:
Info(){};
Info(int i){new (this) Info(); type = i;}
Info(char e){new (this) Info(); name = e;}
~Info(){}
private:
initRest(){}
int type;
char e;
};
这里我们使用了placement new来强制在本对象地址( this指针所指 地址)上调用类的构造函数。这样一来,我们可以绕过编译器的检查,这种方法很危险,不推荐使用。
以下是委派构造函数实现的例子
class Info
{
public:
Info(){initRest();}
Info(int i) : Info() {this->type = i;}
Info(char e) : Info(){this->name = e;}
~Info(){}
private:
void initRest(){}
int type {1};
char name {‘a’};
};
在 Info( int) 和 Info( char) 的初始化列表的位置, 调用 了“ 基准版本” 的 构造函数 Info()。为了区分被调用者和调用者,称在初始化列表中调用“基准版本” 的构造函数为委派构造函数( delegating constructor),而被调用的“ 基准版本” 则为目标构造函数( target constructor)。 在 C++ 11 中, 所谓委派构造, 就是指委派函数将构造的任务委派给了目标构造函数来完成这样一种类构造的方式。在上面代码中, 委派构造函数只能在函数体中为 type、 name 等成员赋 初值。 这是由于委派构造函数不能有初始化列表造成的。 在 C++ 中, 构造函数不能 同时“ 委派” 和使用初始化列表, 所以如果委派 构造函数要给变量赋初始值,初始化代码必须放在函数体中。
struct Rule
{
int i;
Rule(int a) : i(1){}
Rule() : Rule(10), i(1){} //无法通过编译
}
上面的代码由于不能在初始化列表中既初始化成员, 又委托其他构造 函数完成构造。故而它是无法通过编译的。
以下代码是通过私有化构造函数来进行代码改进:
class Info
{
public:
Info() : Info( 1, 'a') { }
Info( int i) : Info( i, 'a') { }
Info( char e): Info( 1, e) { }
private:
Info(int i, char e): type(i), name(e) {}
int type;
char name;
};
在上面代码中, 我们定义了一个私有的目标构造函数Info( int, char),这个构造函数接受两个参数,并将参数在初始化列表中初始化。 而且由于这个目标构造函数的存在,我们可以不再需要 InitRest 函数了,而是将其代码都放入 Info( int, char) 中。 这样一来, 其他 委派构造函数就可以委托该目标构造函数来完成构造。
三.多个委派构造函数
class Info
{
public:
Info() : Info( 1) { } //委派构造函数
Info( int i) : Info( i, 'a') { } //既是目标构造函数,也是委派构造函数
Info( char e): Info( 1, e) { }
private:
Info( int i, char e): type( i), name( e) { } // 目标构造函数
int type;
char name;
};
以上代码就是这样一种链状委托构造,这里我们使 Info() 委托Info( int) 进行构造,而Info( int) 又委托 Info( int, char) 进行构造。在委托 构造的链状关系中, 有一点程序员必须注意, 就是不能形成委托环( delegation cycle)。
例:
struct Rule2
{
int i, c;
Rule2(): Rule2( 2) {}
Rule2( int i): Rule2(' c') {}
Rule2( char c): Rule2( 2) {}
};
Rule2 定义中, Rule2()、 Rule2( int) 和Rule2( char) 都依赖于别的 构造函数,形成环委托构造关系。 这样的代码通常会导致编译错误。
#include < list>
#include < vector>
#include < deque>
using namespace std;
class TDConstructed
{
template< class T> TDConstructed( T first, T last) : l( first, last) {} list< int> l;
public:
TDConstructed( vector< short> & v):
TDConstructed( v. begin(), v. end()) {}
TDConstructed( deque< int> & d): TDConstructed( d. begin(), d. end()) {}
};
在以上代码中, 我们定义了一个构造函数模板。 而通过两个委派构造 函数的委托, 构造函数模板会被实例化。 T会分别被推导为 vector< short>:: iterator 和 deque< int>:: iterator 两种类型。 这样一来,我们的TDConstructed 类就可以很容易地接受多种容器对其进行初始化。 这无疑比罗列不同类型的构造函数方便了很多。 可以说,委托 构造使得构造函数的泛型编程也成为了一种可能。
四.委托构造的异常处理
函数中使用try的话,那么从目标构造函数中产生的异常,都可以在委派构造函数中被捕捉到。
#include < iostream>
using namespace std;
class DCExcept
{
public:
DCExcept( double d)
try : DCExcept( 1, d)
{
cout << "Run the body." << endl;
}
catch(...)
{
cout << "caught exception." << endl;
}
private:
DCExcept( int i, double d)
{
cout << "going to throw!" << TDConstructed( v. begin(), v. end())
{
}
TDConstructed( deque< int> & d):TDConstructed( d. begin(), d. end()) {}
};