《深度探索C++对象模型》学习笔记
最近学习了c++对象模型,做下笔记,方便日后查阅
第一章:关于对象
- c++对象模型
在此模型中,成员变量被配置在每一个对象中,静态成员变量、成员函数和静态成员函数放置在对象之外,虚函数则以下两个步骤支持:
- 每个类产生一堆指向虚函数的指针放在表格之中,这个表格叫做虚函数表(vtal)
- 每个对象中被添加一个指针(vptr),指向相关的虚函数表,vptr的赋值和重置由每个类的构造函数、析构函数和拷贝赋值运算符自动完成。每个类所关联的type_info放在虚函数表的表头。
内存布局如图:
2.对象的差异
一个对象需要多少内存,sizeof()的结果,包含以下内容
- 非静态成员变量的大小总和
- 内存对齐,额外填补的空间( 32位机器上对齐单位是4个字节)
- 为了支持虚函数,内部额外需要的空间,如虚函数表指针
3.指针类型
指针的大小是固定的,32位机器上是4个字节,存放的是内存地址。指针类型是指引编译器去如何解析某一个特定地址中的内存和大小
第二章:构造函数语意学
- 默认构造函数
默认构造函数会在需要的时候被编译器产生出来,有以下4种情况:
- 成员变量是类类型,并且带有默认的构造函数
编译器需要在构造函数中调用成员变量的默认构造函数,如果该类已经有构造函数,则编译器会扩展原有构造函数,将安插一些代码在原有代码之前,如果没有默认构造编译器会合成默认构造函数- 基类含有默认构造函数
- 该类带有虚函数,声明或继承一个虚函数
编译期间,虚函数表会被编译器产品出来,内放类的虚函数地址,每一个类对象有一个额外的虚函数表指针vptr,会被编译器合成处理,内含虚函数表的首地址- 继承链中含有虚基类
- 拷贝构造函数
一个良好的编译器可以为大部分的class object产生bitwise copies(位逐次拷贝),也就是说,“如果一个类未定义拷贝构造函数,编译器会为它产生一个”这句话是不对的。
默认构造函数和拷贝构造函数在必要的时候才由编译器产生出来。条件同默认构造函数的4种情况。 - 位逐次拷贝
什么时候一个类不展现位逐次拷贝,有以下四种情况:
- 当类内含有一个类类型的成员变量,并该成员变量声明了一个拷贝构造函数时(不论是声明还是编译器合成的)
- 当类继承自一个基类,基类含有拷贝构造函数,(不论是声明还是编译器合成的)
- 当类声明一个或多个虚函数时
- 当类派生子一个继承串链,其中有一个或多个虚基类时
- 重新设定虚函数表指针vptr
在默认构造函数、拷贝构造函数和拷贝赋值函数中,编译器会明确设定vptr的值指向正确的虚函数表首地址 - 成员初始化列表
当你写下一个构造函数时,你有机会设定成员变量的初值,要么通过成员初始化列表,要么在构造函数体内,除了四种情况,这两种用法都差不多
- 当初始化一个引用的成员变量时
- 当初始化一个const成员变量时
- 当调用一个基类的构造函数,它拥有一组参数时
- 当调用一个成员变量的构造函数,它拥有一组参数时
对于类类型,放在初始化列表中和构造函数体内,效率差异较大,如下:
class Word{
String _name;
int _cnt;
public:
Word(){
_name=0;
_cnt=0;
}
}
// 编译器视角
Word::Word(){
_name.String::String();
String temp = String(0);
_name.String::operator=(temp);
temp.String::~String();
_cnt = 0;
}
Word::Word:_name(0){
_cnt=0;
}
// 编译器视角
Word::Word(){
_name.String::String(0);
_cnt=0;
}
放在成员初始化列表中,会少一次临时对象的创建、析构和拷贝赋值运算符操作。
还有成员初始化列表的初始化顺序是声明的顺序而不是初始化列表中的顺序。
第三章:Data语意学
- Data Member的绑定
类外有一个全局变量和类内成员变量同名时,在类内的函数中返回该变量名,返回的是哪个值呢?答案是类内的成员变量,对成员函数分析时,会在整个类声明完成才开始的。但成员函数的形式参数不一样,形式参数是第一次遇到的类型决定的,如下面一段代码:
typedef int length;
class Point3d{
public:
void mumble(length val){ _val = val; }
length mumble() { return _val; }
private:
typedef float length;
length _val;
}
在函数mumble()参数length类型是int 类型,而_val是float类型,编译器会不予通过编译&#x