类对象模型
- 对象是有地址的,空对象的尺寸为1字节。
- 所有函数(普通成员函数,静态成员函数,虚函数),静态成员变量属于类,尺寸不计算在对象大小内。
- 虚函数表(vtbl)属于类,不属于对象。
- 虚函数表指针(vptr)属于对象,占用对象的尺寸大小。
- 普通成员属于对象,且多个普通成员之间存在字节对齐。
字节对齐
1.字节对齐是什么?
由于CPU访问内存的地址特性,一个变量如果不对齐,可能会触发更多次CPU访问内存的操作。
2. 内存对齐的规则
有效对齐数= min(自身对齐数,最大字节)
自身对齐数:32位系统为4,64位系统为8
构造函数语义
我们常常认为,当我们没有定义默认构造函数的时候,系统会为我们合成一个默认构造函数。其实只有在必要的时候,编译器才会为我们生成默认的构造函数。
- 类类型成员
类中包含类类型的对象成员,且该成员具有缺省的构造函数,编译器会为该类生成一个合成默认构造函数,作调用类中的类类型成员之用。 - 父类
具有一个父类,父类具有缺省的构造函数,编译器会生成合成默认构造函数,安插相关的代码,调用父类的构造函数。 - 虚函数
具有虚函数。编译器安插代码:
这时候因为有虚函数的存在,编译器为我们生成一个基于该类的虚函数表vftable,并为虚函数表指针赋值。 - 虚基类
虚基类的情况存在于三层结构:一个grand派生出两个parent类,在子类中同时继承这两个parent类。这时候由于grand在子类中存在冲突,因为需要把grand声明为一个虚基类的情况。
这种情况下,我们的编译器产生合成构造函数来生成虚基类表vbtable. - 拷贝构造函数语义
在普通的场景下,不写拷贝构造函数的时候,编译器会有内部的直接拷贝数据的手法(bitwise),将类中的普通变量,类的类变量的每个成员变量,此时编译器并没有为我们生成拷贝构造函数。
那么在什么场景下编译器会为我们生成拷贝构造函数呢?
1.类中具有类类型的变量,且该类变量具有拷贝构造函数。这时候会生成拷贝构造函数完成额外的工作–>调用类变量中的拷贝构造函数。
2.父类具有拷贝构造函数。
3.具有虚函数。生成拷贝构造来设定虚函数表指针。
4.虚基类
程序转化语义
在编译器解析的角度来讲,我们的代码是如何解析的。
- 定义时初始化对象
class A
{
public:
A(const A& a)
{
}
int m;
}
A a0;
A a0.m = 1;
A a1 = a0;
A a2(a0);
A a3 = a1;
在定义时初始化这件事情上,编译器是不会调用构造函数的,直接调用拷贝构造函数完成对象的构建。
- 类类型的函数参数
函数形参上直接调用拷贝构造构造函数形参。 - 函数返回值初始化
系统产生一个临时对象作为返回值,且返回值是直接拷贝构造在被初始化的值上的。
编译器角度上这点是如何做的呢?相当于返回值作为一个对象的引用传入,而传入的对象调用拷贝构造函数。
A func()
{
A temp;
return temp;
}
A a = func();
//编译器的做法
void func(A& extra)
{
A temp;
extra.A::A(temp);
return ;
}
程序优化
- 类类型的函数返回值
- 开发层面的优化
节省一个拷贝构造和析构函数
CTemp Double()
{
//方式1
//CTemp tmpm; //消耗一个构造函数,一个析构函数
//return tmpm; //生成一个临时对象,然后调用拷贝构造函数把tmpm的内容拷贝构造到这个临时对象中去,然后返回临时对象。
// //这个临时对象也消耗了一个拷贝构造函数 ,消耗了一个析构函数;
//
//方式2 直接在返回的临时对象上构造
return CTemp(); //生成一个临时对象。
}
- 编译器层面的优化
上述代码方式1中,经过编译器优化之后,实际上并不会构造临时对象。而自动被编译器优化成方式2的构造方式,这种方式被称为 RVO(Return Value Optimization)优化;
- 用类对象初始化另一个类对象
在这种情况下,编译器生成的拷贝构造函数除了bitwise,vptr的赋值,类类型与父类拷贝构造函数的调用等等之外,在有数据需要深拷贝的场景下,我们也需要自己生成一个拷贝构造函数。
成员初始化列表
必须成员初始化列表使用场景
- 类成员变量需要定义的时候初始化的类型:引用类型,const变量。
- 带参构造函数的类类型,可以是基类,也可以使类类型成员。
成员初始化列表的特点
1.成员初始化列表属于编译过程中编译器往构造函数体之前插入的片段,对于类类型的成员变量,相对于构造函数体中赋值,成员初始化列表可以使类类型成员变量在构造的时候初始化,避免构造函数和拷贝赋值运算符调用从而提高效率。
2.成员初始化列表中的初始化顺序,取决于在类中的定义顺序,当使用一个变量初始化另一个变量的时候,更应考虑到这一点。