您的位置:首页 > 编程语言 > C语言/C++

委托构造函数 (来自C++11)

2016-05-31 07:17 330 查看

委托构造函数delegating constructor

Herb Sutter & Francis Glassborow

1        问题的背景

1.1     
简介

C++没有提供让一个构造函数去委托另一个构造函数执行构造操作的机制。这意味着不能(或不提倡)使用缺省参数,类的维护者不得不编写并维护多个构造函数。这会导致源代码和目标代码的重复,降低了可维护性(由于可能引起不一致性),有时还会导致代码膨胀。

其它的OO语言,如Java,就提供了这种特性。C++有必要增加相应的特性。

1.2     
现状

目前,C++书籍建议用如下代码来让多个构造函数委托执行相同的初始化操作:

class X {
void CommonInit();
Y y_;
Z z_;
public:
X();
X( int );
X( W );
};
X::X() : y_(42), z_(3.14) { CommonInit(); }
X::X( int i ) : y_(i), z_(3.14) { CommonInit(); }
X::X( W e ) : y_(53), z_( e ) { CommonInit(); }
这种做法存在以下问题:

l         构造函数体代码重复。在这个例子中,有一个构造函数可以使用缺省参数来取替。而另一个不行,至少在不改变调用语义的前提下不行。

l         数据成员初始化重复。目前也不能委托数据成员的变量初始化,除非对这个类进行重构(如,把数据成员分离到另一个类,然后用CommonInit()分配并持有它的指针)。不可能在没有语言支持的前提下达到这个效果,因为一旦我们进入一个非构造函数就已经太迟了,所有数据成员都已经构造完成。所以没有办法“真正”委托任何东西,包括数据成员的构造。

注意,有些初学者(错误地)以为委托构造函数的特性已经存在,原因是以下代码可以通过编译,虽然它不能实现他们的期望:

class X {
int i_;
public:
X();
X( int );
};
X::X() { DoSomethingObservableToThisObject(); }
X::X( int i ) : i_(i) { X(); } //
可以编译,但什么也不做!

2        建议

2.1     
简单案例

我们建议让类X的某个构造函数(称为“委托构造函数delegating constructor”)可以在初始化列表中调用同类的另一个构造函数(称为“目标构造函数target
constructor”)。即委托构造函数把对象的初始化委托给另一个构造函数,然后再取回控制并执行其它的操作。一个委托构造函数也可以是另一个委托构造函数的目标构造函数。

例如:

class X {
int i_;
public:
X( int i ) : i_(i) { }
X() : X(42) { } // i_ == 42
};
规则如下:

l         允许外联形式的定义(见下例)。

l         最多只能有一个目标构造函数。如果在初始化列表中有一个同类的构造函数,则该初始化列表中不能再有其它东西(即不允许有其它基类或数据成员的初始化)。目标构造函数采用通常的重载决议和模板参数推断来选择。

l         目标构造函数还可以再委托给另一个构造函数。如果存在递归循环(如,构造函数C1委托给另一个构造函数C2,而C2又委托给C1),则行为未定义。不要求编译器检查这种情况,因为构造函数可以定义在不同的编译单元中,检查这种递归循环通常要耗费大量的编译时间。当然编译器如果能够进行检查,也是被鼓励的。

l         委托构造函数体中的语句在目标构造函数完全执行后才被执行。目标构造函数体中的局部变量不在委托构造函数体中起作用。

l         对象的生命期从任意一个构造函数执行完毕开始(对于委托构造的情况,就是最终的目标构造函数执行完毕时)。[C++03] §3.8中所写的“构造函数调用结束”是指任意一个构造函数。这意味着从委托构造函数体中抛出异常将导致析构函数的自动执行。

例子一:

class X {
X( int, W& );
Y y_;
Z z_;
public:
X();
X( int );
X( W& );
};
X::X( int i, W& e ) : y_(i), z_(e) { /*Common Init*/ }
X::X() : X( 42, 3.14 )
{ SomePostInitialization(); }
X::X( int i ) : X( i, 3.14 )
{ OtherPostInitialization(); }
X::X( W& w ) : X( 53, w )
{ /* no post-init */ }
 
X x( 21 ); // if the construction of y_ or z_ throws, X::~X is invoked
例子二:

class FullName {
string firstName_;
string middleName_;
string lastName_;
public:
FullName(string firstName, string middleName, string lastName);
FullName(string firstName, string lastName);
FullName(const FullName& name);
};
FullName::FullName(string firstName, string middleName, string lastName)
: firstName_(firstName), middleName_(middleName), lastName_(lastName)
{
// ...
}
// delegating copy constructor
FullName::FullName(const FullName& name)
: FullName(name.firstName_, name.middleName_, name.lastName_)
{
// ...
}
// delegating constructor
FullName::FullName(string firstName, string lastName)
: FullName(firstName, "", lastName)
{
// ...
}
例子三:

class ex {
ex(int =0, double = 0.0, float = 0.0, std::string = "");
ex(int, double, std::string);
ex(int, std::string);
private:
int j;
double d;
float f;
std::string s;
};
ex::ex(int jp, double dp, float fp, std::string sp)
: j(jp), d(dp), f(fp), s(sp)
{
std::string message("full ctor");
std::cout << message <<'/';
}
ex::ex(int jp, double dp, std::string sp)
: ex(jp, dp, 1.0, sp)
{
std::string message("float defaulted ctor");
std::cout << message << '/';
}
ex::ex(int jp, std::string sp)
: ex(jp, 0.0, sp)
{
std::string message("float & double defaulted ctor");
std::cout << message << '/n';
}
例子三中的最后一个构造函数将象如下方式执行:

ex::ex(int jp, std::string sp)
: j(jp), d(0.0), f(1.0), s(sp)
{
{
std::string message("full ctor");
std::cout << message <<'/';
}
try {
std::string message("float defaulted ctor");
std::cout << message << '/';
} catch(…) { ~ex(); throw; }
try {
std::string message("float & double defaulted ctor");
std::cout << message << '/n';
} catch(…) { ~ex(); throw; }
}
(注意,如果是构造函数try块的情形,请参见后面的2.3节)。

注意,有时也需要委托给带有更少参数的构造函数。例如std::fstream就是一个很好的例子。在标准中std::fstream有两个构造函数:

basic_fstream();
explicit basic_fstream(const char* s, ios_base::openmode mode);
而后一个构造函数可以写成委托构造函数:

basic_fstream::basic_fstream( const char* s, ios_base::openmode mode)
: basic_fstream()
{
if(open(s, mode) == 0)
setstate(failbit);
}

2.2     
模板构造函数

如果使用模板构造函数作为目标构造函数,那么推断的方法如常,也可以显式给出模板参数。例如:

class X {
template<class T> X( T first, T last ) : l_( first, last )

{ /*Common Init*/ }
list<int> l_;
public:
X( vector<short>& );
X( deque<char>& );
};
X::X( vector<short>& v ) : X( v.begin(), v.end() )
{ }
// T 被推断为 vector<short>::iterator
X::X( const deque<char>& d )
: X<deque<char>::iterator>( d.begin(), d.end() )
{ }
// T 无需推断

2.3     
构造函数try块

如果使用了构造函数try块,那么目标构造函数的执行就象普通的数据成员初始化一样;从初始化列表或目标构造函数体中抛出异常都意味着不会进入委托构造函数体,如果存在合适的处理句柄,异常就会被委托构造函数try块所捕获。例如:

class X {
X( Y&, int, double );
Y y_;
int i_;
public:
X( double, Y );
X( Y );
};
 
X::X( Y& y, int i, double d )
try : y_( y*d ), i_(i)
{ cout << “X::X(Y&,int,double) body” << endl;
throw 1; }
catch(…)
{ cout << “X::X(Y&,int,double) catch” << endl; } //
隐式的重新抛出
 
X::X( double d, Y y )
try : X( y, 42, d )
{ cout << “X::X(double,Y) body” << endl; }
catch(…)
{ cout << “X::X(double,Y) catch” << endl; } //隐式的重新抛出
 
X::X( Y y )
try : X( 3.14, y )
{ cout << “X::X(Y) body” << endl; }
catch(…)
{ cout << “X::X(Y) catch” << endl; } //隐式的重新抛出
 
int main() {
X x( Y() );
}
 
// 输出结果
X::X(Y&,int,double) body
X::X(Y&,int,double) catch
X::X(double,Y) catch
X::X(Y) catch
在以上例子中,最后一个构造函数的执行就象以下伪代码一样:

X::X( Y y )
try {
try {
try : y_( y*3.14 ), i_(42)
{ cout << “X::X(Y&,int,double) body” << endl;
throw 1; }
catch(…)
{ cout << “X::X(Y&,int,double) catch” << endl;
throw; }
}
{
cout << “X::X(double,Y) body” << endl;
}
catch(…) {
try { cout << “X::X(double,Y) catch” << endl;
throw; }
catch(…) { ~X(); throw; }
}
}
{
cout << “X::X(Y) body” << endl;
}
catch(…) {
try { cout << “X::X(Y) catch” << endl;
throw; }
catch(…) { ~X(); throw; }
}

3        影响与实现

3.1     
影响

这个语言特性与语言的其它部分配合很好,是初始化列表语法及语义的一个自然的扩展。对模板的影响也一样。对于使用构造函数的代码没有冲突。对已有代码没有影响。

3.2     
实现

实现这一特性不存在已知的或可预见的困难。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: