《深度探索C++对象模型》读书笔记(1)仅含前四章

来源:互联网 发布:c 编程软件 编辑:程序博客网 时间:2024/06/05 08:06

1.C++对虚拟基类的支持会导致一点额外的负担,具体实现是在派生类对象中使用一个指针来指向对应的虚拟基类,这个指针指向一个表格,表格存放的是引导访问虚拟基类子对象的信息,例如其在派生类对象中的偏移量或者内存地址,这取决于编译器实现。

 

2.没有任何数据成员的类,为了在内存中取得唯一的内存地址,所以会由编译器插入一个空的byte。但是这个优化仅仅是在没有任何数据成员的类身上实现。(VS2017中这个优化已经不存在了)

 

3.typedef得到的别名应该写到所有使用到这个别名的类成员声明的前面,这样可以避免发生跟类外的重名发生。

 

4.


 

5.静态数据成员被引用在编译器中会被直接替换为对data segement的数据的直接引用,一般不经过类对象引用。但是在C++语法上存在一个可能,那就是通过函数返回值引用静态数据成员,这个时候单纯地直接替换就会造成函数在实际上并没有执行。对于这个情况的处理一般来说就分拆开两部,先调用函数,然后再直接引用。


 

6.对静态数据成员取指针得到的不是类成员的指针,而是该成员的数据类型的指针。ps:类成员指针其实是其类内偏移值

 

7.由于对非静态数据成员进行存取是通过类对象地址+偏移量来寻址的,而偏移量是编译器就可以计算获得的,所以存取一个非静态数据成员效率跟普通地去存取C风格的结构体数据是一样的,即使这个非静态数据成员来自一个只包含普通基类的继承链。(继承链中包含虚拟基类情况下这个结论不成立)

 

8.C++通过在类对象中插入vptr来支持多态,这个vptr指向一个vtable,当派生类对象被创建的时候,在构造函数里面会根据实际情况重写vtable的内容和vptr数据。

 

9.单一继承由于结构简单,一般内存布局上基类子对象放在派生类对象的开头,所以两者的地址应该是一样,这是天然的多态,只需要对该指针进行不同的解析就好。多重继承由于内部存在多个不同的基类子对象,自然基类子对象的地址也是不一样的,要对这样的派生类执行多态,一般是编译器把相关的基类子对象指针操作内在地使用头地址+offset的方式进行。

 

10.虚拟继承一般的实现方法:把含有虚拟基类的类数据分成不变局部和共享局部,不变局部一般是指非虚拟基类数据成员的部分,为了实现共享局部的共享,一般就是间接存取,以保证不同情况的虚拟继承能有相同的布局。

 

11.虚拟继承为了实现共享局部的共享,一般都是在类内部保存一份共享局部的数据,在各个派生类对象内部持有一份访问该共享局部数据的指针。但是当继承链中有超过一个虚拟基类的话,那么需要持有的指针也需要相应地增加。为了解决这个问题,一般有两种常用的策略,一种是需要再增加一层间接存取,建立一个virtual base class table,类同于virtual function table,这样的话无论虚拟基类有多少个,只需要增加virtual base class table的长度就可以了,派生类持有的virtualbase class table指针就只要一个。第二种是把这个virtual base class table建在virtual function table的负向坐标。

上面所做的一切努力都是为了让虚拟继承的内存布局能稳定一些,不会因为继承链过于复杂而复杂程度大幅增加。由于多态只发生在指针或者引用出现的场合,所以一般的类对象存取虚拟基类成员的时候,不需要这么复杂的间接寻址,可以直接在编译期就计算出成员地址。

 

12.对类数据对象取指针得到的是该对象在类内部的偏移值,一般来说这个偏移值会比实际情况大1,这是为了区分指向第一个数据成员的指针和空指针0

 

13.静态成员函数没有this指针,它的出现是为了解决存取静态成员变量的封装问题,具体例子就是单例模式.

对静态成员函数取址得到的是实际的内存地址。

Class A{

Public:

StaticA&Instance()

{

If(_instance == 0)

{

_instance = new A();

}

 

Return *_instance;

}

 

Private:

Static A*_instance;

A();

};

 

14.单一继承对于虚函数的处理比较简单,由于只有一个单一的基类,在编译期就可以把派生类中的vptr在类中的地址确定好。所以相对比较简单。


 

15.多重继承在支持虚函数上主要面临的问题是如果通过指向非第一基类的指针调用虚函数,这个时候必须要在每一个非第一基类内部保存一份vptr,这个vptr指向的vtable与第一基类的vptr指向的vtable的内容有所不同,这是因为这个时候需要动态调整this指针来保证在调用派生类的函数的时候不会使用了非第一基类的this,应该使用派生类的this。非第一基类的vtable要另外保存offset信息来计算出派生类的真正地址。


 

16.虚拟继承跟单一继承的区别在于虚基类的子对象在派生类对象中只能通过vtable的一个offset值来存取,这样有助于统一派生类与其他类的内存布局的一致性。


 

17.一个指向非静态非虚拟成员函数的指针,需要绑定于某个类对象的地址上才能被正确地执行,因为调用中隐含地传入类对象地址作为this指针。由于静态成员函数不需要this指针,所以对应的指针类型应该是一般的“函数指针”,而不是“指向成员函数的指针”。

 

18.对虚函数取址得到的是该函数地址在vtable里面的索引值。这里有个问题,如果类里面有两个函数原型一样仅仅是函数名不一样的函数,一个是虚函数,一个是普通的成员函数,那么在执行ptr->*pmf的时候怎么确定是调用普通的成员函数还是虚函数?对于这个问题各个编译器的处理方法不一样

 

19.由于inline函数需要在调用点原地展开代码,所以有可能增加源程序的大小。

 

20.避免滥用虚拟函数机制,对于在派生类中极小可能被重写的函数,尽量不声明为虚函数,这样会增加调用成本。另外虚函数尽量避免声明为const,因为这样会限制派生类中的函数修改数据成员的能力。最后就是避免把基类的析构函数声明为纯虚函数,一般声明为虚函数。

阅读全文
0 0