实例化对象
影响因素
虚函数
由虚表指针指向,所有对象共享。
非虚函数
所有对象共享。
非静态数据成员
对象大小的决定因素
静态函数
所有对象共享。
静态数据成员
内存对齐
可以手动指定。32位、64位不同。
1 | class Abstract { |
一个实例对象,包含 非静态数据成员、虚表指针、为内存对齐而必须的填充,静态数据成员、函数独立于实例对象。
空对象
1 | class Empty{ |
地址唯一性要求 C++规定每个对象必须有唯一的地址。空类对象大小至少为1字节,只是为了区分实例对象。 空类大小为0会导致数组元素无法正确分隔。
空基类
1 | class Derived : public Empty { |
**空基类优化(Empty Base Optimization, EBO)**派生类的内存布局中,空基类与首个数据成员共享地址空间。
内存对齐
手动指定
1 |
|
手动修改内存对齐,作用域级别
1 | __attribute__((aligned(n))) // 单独设置 n 字节对齐,GCC/Clang 特有(非标准 C++) |
手动修改内存对齐,声明级别
非静态数据成员声明顺序与内存对齐
相同访问控制级别(public protected private),更 靠后 的成员有更 高 的地址。
即内存布局和声明顺序一致。
对于不同访问控制级别,未作规定,由编译器自行实现。但实际上编译器也是按声明顺序进行处理。
变量顺序会直接影响 内存对齐。
不含虚表的继承
单继承
1 | class A { |
类之间单独计算内存对齐。
C++ 保证,出现在 derived class 中的 base class subobject 有其 完整原样性。
否则,大的 object 赋值给小的 object,会引发 object 切割,将 大的object 的 subobject 赋值给 小的 object。
多继承
1 | class A {}; |
多重继承
1 | class A {}; |
含虚表的继承
1 | class A { |
构造函数语义
默认构造生成规则
一些情况下,编译会定义 默认构造、拷贝构造、移动构造、拷贝复制运算符、移动复制运算符、析构成员函数 。
以下条件满足都满足时,编译器不会提供 默认构造
- 没有
虚函数、虚基类
无需设置虚表 - 所有
非静态数据成员都已经在声明时初始化
无需初始化值 - 所有
直接继承的基类都没有默认构造函数
无需调用基类的构造 - 所有
非静态的数据成员都没有默认构造
无需调用数据成员的构造
基类和数据成员的初始化时机
1 | class A { |
构造是,从 低地址 -> 高地址 去构造
拷贝构造生成规则
以下条件均满足时,编译器不会实现 拷贝构造、移动构造
- 没有
虚函数、虚基类
无需构造虚表 直接继承的基类没有拷贝构造
无需调用基类- 所有 含 类对象(包括容器) 的
非静态数据成员没有拷贝构造
无需调用成员的
析构函数语义
析构函数生成规则
以下条件满足都满足时,编译器不会提供 默认析构
- 所有 含 类对象(包括容器) 的
非静态数据成员没有析构
无需调用成员的析构 - 所有
直接继承的基类析构
无需调用基类的析构
数据成员 析构执行的顺序,与数据成员的 声明顺序 相反。
析构是,从 高地址 -> 低地址 去析构。
1 | class A { ~A(){} }; |
函数调用原理
全局函数
step1 参数传递step2 调用函数step3 处理返回值
非静态非虚成员函数
1 | class A { A(...){} } |
step1 参数传递step2 把对象的首地址作为参数,调用函数step3 处理返回值
构造函数、拷贝构造函数、析构函数、虚函数
step1 参数传递step2 虚表跳转step3 把对象的首地址作为参数,调用函数step4 处理返回值