类具有可派生性,派生类可以自动获得基类的成员变量和接口(通过虚函数和纯虚函数)。不过基类的非虚函数则无法再被派生类使用了。这条规则对于类中构造函数也不例外,如果派生类要使用基类的构造函数,通常需要在构造函数中显式声明。如下:
struct A { A(int i) {} };
struct B : A { B(int i): A(i) {} };
如果在派生类中要写的构造函数完完全全就是为了构造基类,那么此时需要写很多"透传"的构造函数。
事实上,在 C++ 中已经有了一个好用的规则,就是如果派生类要使用基类的成员函数的话,可以进行 using
声明。这种做法在 C++11 中被扩展到了构造函数上,这样派生类可以通过使用 using 声明来声明继承基类的构造函数。
struct A {
A(int i) {}
A(double d, int i) {}
A(float f, int i, const char* c) {}
// ...
};
struct B : A {
using A::A; // 继承构造函数
// ...
virtual void ExtraInterface() {}
};
另外,C++11 标准继承构造函数被设计为跟派生类中的各种类默认函数(默认构造、析构、拷贝构造等)一样,是隐式声明的。不过继承构造函数只会初始化基类中成员变量,对于派生类中的成员变量,则需要另外初始化。
对于继承构造函数来讲,参数的默认值是不会被继承的。事实上,默认值会导致基类产生多个构造函数的版本,这些函数版本都会被派生类继承。因此程序员在使用有参数默认值的构造函数的基类的时候,必须小心。个人不建议在构造函数中对传参使用默认值,可使用变量初始化代替。
当派生类有多个基类的时候,可能会产生继承构造函数"冲突"。如下:
struct A { A(int) {} };
struct B { B(int) {} };
struct C: A, B {
using A::A;
using B::B;
};
在上面的代码中,A 和 B 的构造函数会导致 C 中重复定义相同类型的继承构造函数。
这种情况下,可以通过显式定义继承类的冲突的构造函数,阻止隐式生成相应的继承构造函数来解决冲突。如下:
struct C: A, B {
using A::A;
using B::B;
C(int) {}
};
另外,如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数。此外,如果一旦使用了继承构造函数,那么编译器就不会再为派生类生成默认构造函数了。
委派构造函数允许在同一个类中一个构造函数可以调用另外一个构造函数,从而可以在初始化时简化变量的初始化。
为了区分被调用者和调用者,称在初始化列表中调用的构造函数为委派构造函数,而被调用的则为目标构造函数。在 C++11 中,所谓委派构造,就是指委派函数将构造的任务委派给了目标构造函数来完成这样一种类构造的方式。
在 C++ 中,构造函数不能同时"委派"和使用初始化列表,也就是说,如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。比如:
struct Rule1 {
int i;
Rule1(int a): i(a) {}
Rule1(): Rule1(40), i(1) {} // compile failed
Rule1(): Rule1(40) { i = 1; } // compile pass
};
使用委派构造函数时,要注意不能形成一个环,否则会在运行期抛出异常。
委托构造的一个很实际的应用就是使用构造模板函数产生目标构造函数。