C++——虚函数(Virtual Member Functions) 【functions语意学】

单继承下的虚函数

虚函数的实现:

  • 为每个有虚函数的类配一张虚函数表(virtual table),它存储该类类型信息和所有虚函数执行期的地址。
  • 为每个有虚函数的类插入一个指针(vptr),这个指针指向该类的虚函数表。
  • 给每一个虚函数指派一个在表中的索引。

用这种模型来实现虚函数得益于在C++中,虚函数的地址在编译期是可知的,而且这一地址是固定不变的。而且表的大小不会在执行期增大或减小。

一个类的虚函数表中存储有类型信息(存储在索引为0的位置)和所有虚函数地址,这些虚函数地址包括三种:

  • 这个类定义的虚函数,会改写(overriding)一个可能存在的基类的虚函数实体——假如基类也定义有这个虚函数。
  • 继承自基类的虚函数实体,——基类定义有,而这个类却没有定义。直接继承之。
  • 一个纯虚函数实体。用来在虚函数表中占座,有时候也可以当做执行期异常处理函数。

每一个虚函数都被指派一个固定的索引值,这个索引值在整个继承体系中保持前后关联,例如,假如z()在Point虚函数表中的索引值为2,那么在Point3d虚函数表中的索引值也为2。

当一个类单继承自有虚函数的基类的时候,将按如下步骤构建虚函数表:

  1. 继承基类中声明的虚函数——这些虚函数的实体地址被拷贝到继承类中的虚函数表中对于的slot中。
  2. 如果有改写(override)基类的虚函数,那么在1中应将改写(override)的函数实体的地址放入对应的slot中而不是拷贝基类的。
  3. 如果有定义新的虚函数,那么将虚函数表扩大一个slot以存放新的函数实体地址

多重继承下的虚函数

在多重继承下,继承类需要为每一条继承线路维护一个虚函数表(也有可能这些表被合成为一个,但本质意义并没有变化)。当然这一切都发生在需要的情况下。

当使用第一继承的基类指针来调用继承类的虚函数的时候,与单继承的情况没有什么异样,问题出生在当以第二或后继的基类指针(或引用)的使用上。例如:

//假设有这样的继承关系:class Derived:public base1,public base2;
//base1,base2都定义有虚析构函数。
base2 *ptr = new derived;
//需要被转换为,这个转换在编译期完成
base2 *ptr = temp ? temp + sizeof(base1) : 0 ;

如果不做出上面的转换,那么 ptr 指向的并不是 derived 的 base2 subobject
。后果是,ptr 将一个derived类型当做base2类型来用。

当要delete ptr时又面临了一次转换,因为在delete ptr的时候,需要对整个对象而不是其子对象施行delete运算符,这期间需要调整ptr指向完整的对象起点,因为不论是调用正确的析构函数还是delete运算符都需要一个指向对象起点的指针,想一想给予一个derived类的成员函数指向base2 subobjuect 的this指针会发生什么吧。因为ptr的具体类型并不知道,所以必须要等到执行期来完成

有一种叫做thunk的方法,thunk的作用在于:

  1. 以适当的offset值来this调整指针.
  2. 跳到虚函数中去。

Thunk技术即是:虚函数表中的slot仍然继续放一个虚函数实体地址,但是如果调用这个虚函数需要进行this调整的话,该slot中的地址就指向一个Thunk而不是一个虚函数实体的地址。

书中纷杂的讲到不少中种情况,但我以我的理解,做如下小结:

多继承下的虚函数,影响到虚函数的调用的实质上为this的调整。而this调整一般为两种

  1. 调整指针指向对应的subobject,一般发生在继承类类型指针向基类类型指针赋值的情况下。
  2. 将指向subobject的指针调整回继承类对象的起始点,一般发生在基类指针对继承类虚函数进行调用的时候。

第一点,使得该基类指针指向一个与其指针类型匹配的子对象,唯有如此才能保证使得该指针在执行与其指针类型相匹配的特定行为的正确性。比方调用基类的成员,获得正确的虚函数地址。可以想象如果不调整,用ptr存取base2 subobject的数据成员时,会发生什么?调用base2的成员函数的时候,其成员函数接受的this指针指向derived
类型对象,这又会发生什么?结果是整个对象的内存结构有可能都被破坏。还有别忘了,vptr也可以看做一个数据成员,要找到虚函数,前提是获取正确的vptr偏移量。

而第二点,显然是让一个继承类的虚函数获取一个正确的this指针,因为一个继承类虚函数要的是一个指向继承类对象的this指针,而不是指向其子对象。

第一顺序继承类之所以不需要进行调整的关键在于,其subobject的起点与继承类对象的起点一致。

(我自己画了一个图:


虚拟继承下的虚函数

Lippman说,如果一个虚基类派生自另一虚基类,而且它们都支持虚函数和非静态数据成员的时候,编译器对虚基类的支持就像迷宫一样复杂。其实我原想告诉他,我是怀着一颗勇士之心而来的你说呢?你说呢?

虽然书中没有介绍太多,但不难猜测的是在虚继承情况下,复杂点在仍旧在于this指针的调整,然而其复杂度显然又在多继承之上,因为又多了一个vbptr了。



原文链接:(作者写的很好)http://www.roading.org/develop/cpp/c之虚函数virtual-member-functions.html

猜你喜欢

转载自blog.csdn.net/qq_33890670/article/details/80280722