Chapter 1
class object的内存占用
包含以下内容:
- nonstatic data members的大小
- 由于对齐所需要的填充
- 为了支持virtual而产生的额外负担
指针
指针占用的大小总是占据一个字,在32位系统上就是32位,64位系统上就是64位。引用的实现其实就是一个指针,所以占用和指针一致。
Chapter 2
默认构造函数
当需要调用其他内含class object对象的默认构造函数的时候,就需要为该类生成默认构造函数,而其他不存在默认构造函数的对象就得由用户来手动初始化,比如说指针。
如果用户制定了默认构造函数,但是其中有些class object未被调用构造函数,编译器则会自动扩充代码来调用默认构造函数。
存在多个class object时,默认构造函数的调用顺序是按照声明顺序,如果已有的话,则会按照该顺序扩充,哪怕在成员构造列表中已经调用了。对于继承的情况而言,也是同样的。
如果存在虚函数,那么也必须生成一个默认构造函数,同时还有虚函数表和虚函数指针,如果有实现也要扩张。
如果存在虚基类,那么由于无法在编译时确定成员或者函数的位置,所以会构建一个基类指针来指向对应的虚基类的地址,加入一层间接性来使得对虚基类的访问能够被确定。
总的来说,不是说任何class都会生成默认构造函数,也不会指定每一个成员的初始值。生成默认构造函数的目的是确保需要初始化的对象被初始化,以及无法在编译时确定的多态对象能够提供一个稳定的访问方式(比如虚函数、虚基类)
复制构造函数
以下三种情况会调用复制构造函数:
- 显示初始化
- 当做参数交给某个函数
- 传回一个class object
当没有提供复制构造函数的时候,编译器会实现,对于内建类型采用值拷贝的方式,对于类成员对象则是递归地调用复制构造函数。
能否产生复制构造函数取决于class是否展现出位逐次拷贝语义,如果有的话,就不需要生成,直接按位复制(memcpy)即可,反之则需要生成。有四种情况需要:
- 存在一个类成员有复制构造函数(无论以什么方式存在)
- 继承自一个存在复制构造函数的基类
- 存在虚函数
- 存在虚基类
前两种情况是因为要调用对应的复制构造函数,所以才需要生成。
对于第三种情况,因为存在将继承类对象直接赋值给基类对象的存在,这个时候必须重新设置虚函数表,而不是进行逐位复制,所以必须生成。
对于第四种情况,因为需要确定虚基类指针/偏移,对于继承类赋值基类的情况,也是需要明确定位的,所以必须生成。
程序转化语义
在声明class object的时候,申请内存和初始化是分开的,也就是说,是先申请内存,然后再在这个“内存”上调用构造函数,进行初始化。
在将class object作为参数传值的时候,可能会导入临时object,并调用复制构造函数,改写函数传值为传递引用。或者直接将临时变量放到应该在的位置上。
在将class object作为返回值返回的时候,可能将其转化为无返回值函数,然后额外增加一个函数参数,是返回类型的引用。当调用X xx = bar();时,改写为X xx; bar(xx);,然后函数里面则对额外参数做一次复制构造。
编译器可能会采用NRV优化,用传入的额外引用参数代替具名变量,即如果所有return语句返回同一个具名变量,那么将会将对具名变量的修改直接替换为传入的额外对象的修改。