[读书笔记] 深入探索C++对象模型-第二章《构造函数语义学》(上)

整理一下第二章的内容,共之后参考回顾。注:以下部分图片来源于原书

1. 默认构造函数会在需要的时候被编译器产生出来,要注意字眼“需要的时候”,例如如下代码:

class Bat{
public:
	int val;
	Bat* pNext;
	void bat_yell();
};

void Bat::bat_yell()
{
	Bat batMan;
	if(batMan.val || batMan.pNext)
		// ...do something
	// ...		
}

按照程序员的本意,在bat_yell()方法中,batMan会调用默认构造函数将val和pNext初始化为0,但是实际结果并不是这样,程序员的需要并不代表编译器需要,当然,程序员的责任也不该被比那一起所承担,归根结底说来,上述代码片段中,并不会有默认构造函数被合成。退一步讲,即便是有默认的构造函数被合成出来了,也不会将上述的数据成员初始化为0。也就是说此时的默认构造函数是trivial (无用的,不重要的),有四种情况编译器会产生nontrivial(重要的,有用的)的默认构造函数:

a. 带有默认构造函数的类成员对象:


Bar的类成员对象foo含有默认构造函数,所以编译器会为类Bar合成默认构造函数,可能看起来像下面这样:

值得注意的是,即便是编译器为我们合成了默认构造函数,但是并不会产生任何代码来初始化Bar的str成员变量,这个是程序员的责任。那么程序员如果提供了构造函数(默认或者带参数的),编译器会做些什么呢?答案是编译器会扩展以后的构造函数,在每一个构造函数中安插代码,代码会被安插到程序员代码的前面,使得每一个构造函数调用每一个带有默认构造函数的成员变量的默认构造函数,有一点绕,看个具体例子:

b. 第二种情况是基类中由默认构造函数,类似于第一种情况,如果一个没有定义默认构造函数的类继承(派生)于一个带有默认构造函数的基类,那么这个子类的默认构造函数会被合成出来,在其中以按照声明顺序分别调用基类们的构造函数。如果类设计者提供了多个构造函数,但是没有默认构造函数,编译器会扩张已有的构造函数,而不会合成一个新的默认构造函数。如果此类还包含第一种情况:含有存在默认构造函数的成员类对象,那么他们的默认构造函数会在调用基类默认构造函数之后调用。
c. 带有虚函数的类,由于虚函数的存在,会产生虚函数表和虚表指针,而这些必须在对象构造期间安排好,所以对于类所定义的每一个构造函数,编译器会安插一些代码来做这样的事情,对于未声明任何构造函数类,编译器会为他们产生一个默认的构造函数,以便正确的初始化每一个类对象的虚表指针。
d. 继承自虚基类的类,由于在虚拟继承下子类会存在指向虚基类的指针,与第三种情况类似。

总之,以上四种情况编译器会合成一个隐式的重要的默认构造函数(implicit nontrivial default constructor)来满足编译器(而非程序)的需要:调用成员类或者基类的默认构造函数,或者完成每一个对象初始化其虚函数机制或者虚基类机制。对于其他情况并且没有声明任何构造函数的类,他们拥有的是隐式的不重要的默认构造函数(implicit trivial default constructor),实际上并不会被合成出来。
一定要注意,合成出来的默认构造函数只会初始化编译器认为的必要的部分(基类对象,类对象),其他数据成员并不会被初始化。

最后,要记住两个误区(都是不对的):
1. 任何类只要没有定义默认构造函数,就会被合成出来一个。
2. 编译器合成出来的默认构造函数,会为类内的每一个成员设定默认值。
下一篇中整理关于拷贝构造函数(copy constructor)的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值