Preface
多态Tree Easy Pieces:
- 强制类型转换
- 公共前缀
- this指针偏移
这里讨论的cpp的多态是指父类虚函数的执行是由指针具体指向的对象而定。下面将围绕如下例子说明:
1 | class Shape { |
输出:
1 | area Round |
即父类的指针的虚函数s->area()
,由具体指针具体指向的对象(Round r, Cube c
)而定。
多态的关键就是编译器为父类和所有派生类都偷偷地, 在恰当的位置, 创建了一个虚函数表指针。而且虚函数表指针是类内存模型地第一位,所以实际类的内存模型如下:
1 | class { |
然后虚函数表中保存所属类的函数指针,如Round
类,那它的虚函数表就是:
1 | vtable { |
上述的关键点是:
- 类的内存模型:虚函数表在内存中的位置
- 虚函数表维护一系列函数指针,具体指向根据其所属类而定
- 类成员函数自动传入this指针
破解多态
强制类型转换
通过类的内存模型的描述,我们可以知道虚函数表的位于类的第一个成员。也许不是第一个,但一定是属于子类父类共有的前缀。理解子类父类共有的前缀这点很重要,因为一旦”前缀”相同就可以进行强制类型转换!
强制类型转换一个通俗的理解是:子类继承自,包含父类的内容,子类转换成父类就将属于父类的那段空间截取出来就行了。
1 | 0 ┌──────────┐ |
公共前缀:vtable
理解了强制类型转换的思想,那么如果我们想要在发生类型转换后能够访问到我们的”vtable”,那么这个”vtable”就应该放在,内存模型的公共前缀部分。
所以编译器会自动添加虚函数表成员后,所以强制类型转换的结果就是:
1 |
|
这样是父类指针Shape *s
是由哪个子类强制类型转换得来的,那他的vtable就会被该子类的vtable替换掉。从而实现”动态虚函数表”的效果。
this指针偏移
好的,我已经理解了多态如如何动态调用函数了,那这个动态调用的函数又是如何动态拿到正确的成员的?比如Shape *s = &c
后,调用Cube
类的area()
函数并访问Cube类的h
成员。
其实虚函数表是个指针,也就是说的其大小的确定的,而类成员函数由会自动传入this
指针,所以将this
指针加上vtable*
指针占用的空间的偏移量后就转换为了普通访问类/结构体的问题了。