继承构造函数

C++ 中自定义类型——类,是C++ 面向对象的基石。类具有可派生性,派生类可以自动获得基类的成员变量和接口。不过基类的非虚函数则无法再被派生类使用了。

如果派生类 要使用基类的构造函数,同城需要在构造函数中显示声明,但是这也带来一个问题:有的时候,我们的基类可能拥有数量众多的不同版本的构造函数,其派生类去构造基类的时候就需要写大量的“透传”构造函数。

struct A
{
   
	A(int i){
   }
	A(double d,int i){
   }
	A(float f,int i, const char* c){
   }
}

struct B: A
{
    
	B(int i):A(i){
   }
	B(double d,int i):A(d,i){
   }
	B(float f,int i,const char* c):A(f,i,c){
   }	
};

可以看到,这样写很累而且非常的不银杏。事实上,在C++中已经有了一个好用的规则,就是如果派生类要使用基类的成员函数的话,可以通过using声明来完成。

#include <iostream>
using namespace std;

struct Base
{
   
	void f(double i){
   cout<<"base:"<<i<<endl;}
};

struct Derived: Base
{
   
	using Base::f;
	void f(int i){
   cout<<"derived:"<<i<<endl;}
}

int main()
{
   
	Base b;
	b.f(4.5);		//base:4.5

	Derived d;
	d.f(4.5);		//derived:4.5
};

这里我们使用using声明,这样派生类中就拥有了两个版本的重载函数 f 。

在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;		//继承构造函数 
};

更为精巧的是,C++ 11标准继承够着函数被设计为跟派生类中各种类默认函数一样,是隐式声明的。这意味着一个继承构造函数不被相关的代码使用,编译器就不会为其产生真正的函数代码。

委派构造函数

与继承构造函数类似,委派构造函数也是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();}

private:
	void InitRest(){
   ...}
	int type;
	char name;
};

//成员初始化方式
class Info
{
   
public:
	Info() {
   InitRest();}
	Info(int i):type(i) {
   InitRest();}
	Info(char e):name(e) {
   InitRest();}

private:
	void InitRest(){
   ...}
	int type{
   1};
	char name{
   'a'};
};

我们可以看到,这两种写法使得代码越来越精简,不过每个函数还是需要写一遍InitRest()

那么如何去解决这个问题呢?看如下代码:

	Info() {
   InitRest();}
	Info(int i):type(i) {
   this->Info();}
	Info(char e):name(e) {
   this->Info();}

但是这个也有问题:一般的编译器都会阻止this->Info()的编译。所以,我们可以写一个更有“黑客精神”的版本:

//这个代码有危险嗷
	Info() {
   InitRest();}
	Info(int i):type(i) {
   new (this) Info();}
	Info(char e):name(e) {
   new (this) Info();}

但是有了C++ 11,我们duck不必这样,直接使用委派构造函数的方法即可:

class Info
{
   
public:
	Info() {
   InitRest();}
	Info(int i):Info() {
   type = i;}
	Info(char e):Info() {
   name = e}

private:
	void InitRest(){
   ...}
	int type{
   1};
	char name{
   'a'};
}

我们可以看到,这里我一改常态,在函数体内给typename两个变量赋值。这是因为,在C++ 11中构造函数不能同时“委派”和使用初始化列表,所以如果委派构造函数要给变量赋初值,初始化代码必须放在函数体中。

但是要注意一个问题:在构造函数比较多的时候,我们可能不止有一个委派构造函数,而一些目标构造函数很可能也是委派构造函数,这样一来,我们就可能在委派构造函数中形成链状的委派构造关系

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;
}

但是,这种情况往往又容易产生委托环

struct Rule2
{
   
	int i,c;
	Rule2():Rule2(2){
   }
	Rule2(int i):Rule2('c'){
   }
	Rule2(char c):Rule2(2){
   }
}

这里Rule2(int i),Rule2(char c)两个构造函数相互委托,形成环状,会造成编译错误。

参考文献

[1] IBM XL编译器中国开发团队.深入理解C++11.机械工业出版社.2013.06.