C++11中继承构造函数和委派构造函数

1、继承构造函数

在C++继承中,我们可能会遇到下面这个例子

class Base
{
public:
	Base(int va)
		:m_value(va)
	{

	}
	Base(char c)
		:m_c(c)
	{

	}
private:
	int m_value;
	char m_c;
};
class Derived :public Base
{
private:
public:
	//假设派生类只是添加了一个普通的函数
	void display()
	{

	}
	//那么如果我们在构造B的时候想要拥有A这样的构造方法的话,就必须一个一个的透传各个接口,那么这是很麻烦的
	Derived(int va)
		:Base(va)
	{

	}
	Derived(char c)
		:Base(c)
	{

	}
};

上面过程是很麻烦的,但是呢C++11中推出了继承构造函数,使用using来声明继承基类的构造函数,我们可以这样写

class Base1
{
public:
	Base1(int va)
		:m_value(va)
	{

	}
	Base1(char c)
		:m_c(c)
	{

	}
private:
	int m_value;
	char m_c;
};
class Derived1 :public Base1
{
private:
	int m_d{0};
public:
	//假设派生类只是添加了一个普通的函数
	void display()
	{

	}
	//使用继承构造函数
	using Base1::Base1;
};
而且,更神奇的是,C++11标准继承构造函数被设计为跟派生类中的各个类默认函数(默认构造,析构,拷贝构造等)一样是隐式声明的。那么这就意味着如果一个继承构造函数不被相关代码使用,编译器就不会产生真正的函数代码,这样比透传更加节省了空间。

但是有一点问题就是继承构造函数只会初始化基类的成员变量,对于派生类的成员变量就无能为力了,这个时候我们的快速初始化
成员变量就派上用场了,假如上面的Derived1,有一个成员变量m_d

还有一点就是,基类的构造函数可能会有默认值,但是对于继承构造函数来讲,参数的默认值是不会被继承的。所以我们在使用
有参数的默认值的构造函数的基类的时候就必须要小心,我们可以看到下面例子

class A
{
public:
	A(int a = 3, double b = 4)
		:m_a(a), m_b(b)
	{

	}
private:
	int m_a;
	double m_b;
};
那么A中的构造函数会有下面几个版本
A()
A(int)
A(int,double)
A(constA&)

那么B中对应的也就这几个版本了
B()
B(int)
B(int,double)
B(constB&)

有的时候在多继承的情况下,还回出现冲突的情况,比如下面例子

class C
{
public:
	C(int i)
	{

	}
};
class D
{
public:
	D(int i)
	{

	}
};
class E :public C, public D
{
public:
	using C::C;
	using D::D;  //如果不加上其它东西,它是会有警告的,这个时候我们可以给E来一个构造函数
	//我们可以这样
	E(int i)
		:C(i),D(i)
	{

	}
};
我们还要注意的一点就是私有构造是不会被继承的


2、委派构造函数

c++11的委派构造函数是在构造函数的初始化列表位置进行构造的,委派的

假如我们也这样一个例子

class Info
{
private:
	void Init()
	{
		/*一些初始化操作*/
	}
	int type = 3;
	char c = 'D';
public:
	Info()
	{
		Init();
	}
	Info(int i)
		:type(i)
	{
		Init();
	}
	Info(char cc)
		:c(cc)
	{
		Init();
	}
};

这样我们三个构造函数,都调用了Init初始化,这样很麻烦,我们可以利用委托构造函数改写

class Info1
{
private:
	void Init()
	{
		/*一些初始化操作*/
	}
	int type = 3;
	char c = 'D';
public:
	Info1()
	{
		
	}
	Info1(int i)
		:Info1()
	{
		type = i;
	}
	Info1(char cc)
		:Info1()
	{
		c = cc;
	}
};

这样的版本就比上面简单多了

上面的Init()函数被称为目标构造函数

其它两个构造函数被称为委派构造函数

但是呢我们要注意的一点就是我们不能同时使用委派构造函数和初始化列表,例如下面的例子就是错的

Info1(int i)
	:Info1(),type(i)
{
		
}
但是上面的代码却会出现一个错误的例子,比如我们在Init中出现这样的一行代码:type = type + 1;

那么我们调用Info1 f(3);

那么过去我们会先进行委派构造函数,因为就地初始化比列表初始化都快,那么肯定的是type先被就地初始化为3,然后执行3+1等于4

最后执行type = i;将type重新赋值3

那么我们可以看到就地初始化出问题了,那么我们可以在类里面加一个通用的私有的构造函数,我们可以这样写

class Info2
{
private:
	int type;
	char c;
	Info2(int i, char e)
		:type(i), c(e)
	{
		/*其它初始化*/
	}
public:
	Info2()
		:Info2(1, 'a')
	{

	}
	Info2(int i)
		:Info2(i, 'a')
	{

	}
	Info2(char e)
		:Info2(1, e)
	{

	}
};

这样就不会出错了

而在构造函数比较多的时候,我们可能会拥有不止一个委派构造函数,有的函数可能既是目标构造函数,又是委派构造函数的就会出现链式结构,但是我们却不能出现环式结构,环式结构会出现错误

委派构造还有一个很实用的应用就是使用构造模板函数产生目标构造函数

我们可以看到下面的例子

class TDConstructed
{
private:
	template
	TDConstructed(T first, T last)
		:l(first, last)
	{

	}
	list l;
public:
	TDConstructed(vector& v)
		:TDConstructed(v.begin(), v.end())
	{

	}
	TDConstructed(deque& d)
		:TDConstructed(d.begin(), d.end())
	{

	}
};

此外在处理异常方面,如果在委派构造函数中使用try的话,那么从目标构造函数产生的异常,都可以在委派构造函数中被捕捉到






你可能感兴趣的:(C++)