C++11之委派构造函数

系列文章

C++11之正则表达式(regex_match、regex_search、regex_replace)

C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)

C++11之智能指针(unique_ptr、shared_ptr、weak_ptr、auto_ptr)浅谈内存管理

C++11之强制类型转换(static_cast,const_cast,dynamic_cast,reinterpret_cast)

C++11之Lanbda表达式(匿名函数)

C++11之右值引用:移动语义和完美转发(带你了解移动构造函数、纯右值、将亡值、右值引用、std::move、forward等新概念)

C++11之继承构造函数(using 声明)



委派构造函数(delegating constructor)

委派构造函数是在C++11标准新引入的一项改进,主要目的是为了减少程序员在构造函数方面花费的工作。通过委派构造函数使之对于构造函数的编写更加简单。

场景

在下面这段代码中,定义了一个Class Info。这个类中有俩个成员变量,三个构造函数,但是三个构造函数都共同调用了InitRest方法。所以我们看到各个构造函数都有或多或少的相似之处。为此C++11引入了委派构造函数。

class Info
{
public:
	Info() :type(1), name('a')
	{
		InitRest();
	}
	Info(int i) :type(i), name('a')
	{
		InitRest();
	}
	Info(char ch) :type(1), name(ch)
	{
		InitRest();
	}

private:
	void InitRest(){ /* 其他初始化 */ }
	int type;
	char name;
};

使用成员初始化方式进行优化

这里其实我们可以通过成员初始化方式,可以使得初始化列表变得简洁化。

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

private:
	void InitRest() { /* 其他初始化 */}
	int type {1};
	char name {'a'};
};

使用委派构造函数法方式进行优化

首先我们需要了解一下概念:委派构造函数说白了就是将构造的任务分派给一个目标构造函数来完成

委派构造函数:初始化列表中调用“基准版本”的构造函数就是委派构造函数。
目标构造函数:被调用“基准版本”构造函数就是目标构造函数。

需要注意的是委派构造函数不能使用初始化列表 初始化成员变量

// 通过委派构造函数进行优化
class Info
{
public:
	Info()
	{
		InitRest();
	}
	Info(int i) : Info()
	{
		type = i;
	}
	Info(char ch) : Info() // 委派构造函数不能使用初始化列表 初始化成员变量
	{
		name = ch;
	}

private:
	void InitRest() { /* 其他初始化 */}
	int type{ 1 };
	char name{ 'a' };
};

然而委派构造函数也不全是优点,构造函数中委派构造和初始化列表是互斥的,所以变量初始化就只能放在函数体内了。
但是初始化列表的调用总是优先于构造函数,所以这就可能会给自己挖坑

委派构造函数与初始化列表我全都要

为了使用委派构造时还能使用初始化列表,我们可以定义一个private 的目标构造函数,并将初始化列表放在这个private的目标构造函数中,这样其他委派构造函数就可以通过委派这个目标构造来实现构造的功能。

// 委派构造函数 + 初始化列表
class Info
{
public:
	Info() :Info(1, 'a') {}
	Info(int i) : Info(i, 'a') {}
	Info(char ch) : Info(1, ch) {} 

private:
	Info(int i, char ch) :type(i), name(ch) {/*其他初始化信息*/}
	int type{ 1 };
	char name{ 'a' };
};

对上述俩种委派构造进行分析

上述俩种委派构造看似优化了代码,但是却存在着看不到坑。

下面这段测试就会导致预期与实际不否。(注:为了方便测试,将成员变量的作用域设为public)

在这里插入图片描述

这段测试看似结果正确,但真就是我们详细的那样吗?

在这里插入图片描述
分析:
首先Info(int)会委派Info(),然后调用InitRest方法对type进行加1,此时type=2,然后在执行Info(int)构造函数内部,这时type=5。可见这个5”来之不易“啊。

所以这俩种委派构造在这种场景下都不算一个好的代码。

链状委派构造

在构造函数较多情况下,会形成链状的委派构造关系。

// 链状委派构造函数
class Info
{
public:
	Info() :Info(1) {}

	Info(int i) : Info(i, 'a') {}
	Info(char ch) : Info(1, ch) {} 


private:
	Info(int i, char ch) :type(i), name(ch) {}

public:
	int type{ 1 };
	char name{ 'a' };
};

在上面这段代码种,Info()委派Info(int)进行构造工作,然后Info(int)委派Info(int, char)。这就是链状委派

委派环(delegation cycle)

链状委派没有任何问题,但是形成环形链状委派也叫委派环(delegation cycle)就会导致构造死循环

例如下面这个例子Info(int)和Info(char)相互委派。

// 委派环
class Info
{
public:
	Info() :Info(1) {}
	Info(int i) : Info('c') {}
	Info(char ch) : Info(2) {}

private:
	int type{ 1 };
	char name{ 'a' };
};

在VS2022就会提醒你,这个构造函数直接或者间接委派给自己,Info的构造函数创建了一个委派周期。 所以一定要注意委派环
在这里插入图片描述

使用场景

构造模板

使用构造模板函数产生目标构造函数。我们创建了一个class Test,其中有一个private的构造模板,还有俩个委派构造函数,这样就可以实现动态进行构造不同的容器了。有泛式编程那味了。

#include <list>
#include <vector>
#include <deque>
using namespace std;

class Test
{
private:
	template<class T>
	Test(T first, T last):l(first, last){}

public:
	Test(vector<int>& v) :Test(v.begin(), v.end()){}
	Test(deque<int>& d) : Test(d.begin(), d.end()){}

private:
	list<int> l;
};

异常处理

在异常处理中,我们可以在委派构造函数中使用try,然后再目标构造函数中抛出异常,这将可以在委派构造函数中被捕获。

class Test
{
public:
	Test(double d)
		try : Test(1, d)
		{
		cout << "Run the body" << endl;
		}
		catch (...)
		{
			cout << "caught exception." << endl;
		}

private:
	Test(int i, double d)
	{
		cout << "going to throw" << endl;
		throw 0;
	}

	int type;
	double data;
};

int main()
{
	Test info(5);
	return 0;
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值