第四章:虚成员函数(虚函数表thunksplit function和多接入点函数)

Posted 放不下的小女孩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第四章:虚成员函数(虚函数表thunksplit function和多接入点函数)相关的知识,希望对你有一定的参考价值。

 

一、单继承情况

1.虚函数表、指向虚函数表的指针以及类型信息:

  ①为了实现多态,我们需要知道一个指针或引用的真实类型以及其所调用函数实例的位置。所以编译器会创建一个虚函数表,里面存放类的类型和类函数的实例地址,并且在类内存中加入一个指向虚函数表的指针vptr。(为什么不放到类中?导致类的内存不固定,影响计算机内存分配)

  ②虚函数表中的槽(slot):虚函数表中存放类函数的实例地址的地方叫槽slot,每一个函数占有一个槽,访问虚函数则通过槽的位置实现。我们虽然不知道具体哪个实例被调用,但却知道他被存放在哪里(一个槽只存放一个实例地址,因为指针虽然可变,但是必须指向不同对象,而对象是确定的所以对象的虚函数是确定的,指针只是访问不同对象中确定的函数而已)。

  ③派生类的虚函数表:单一继承体系中每一个类只有一个虚函数表(不管几层继承),派生类的虚继承表首先继承基类的所有虚函数的实例(类型信息不拷贝),派生类可以改写这些实例(必须与基类的slot相对应),派生类可以增加新的虚函数放入到虚函数之后的位置,这样虚函数表会增大。

 

 

二、多继承情况

  有三种情况第二或后继的基类会影响对虚函数的支持①通过一个指向第二个基类的指针,调用派生类的虚函数②通过一个指向派生类的指针,调用第二个基类中一个继承而来的虚函数。③允许虚函数的返回值有所变化

  

//第一种情况
Base2 *ptr=new Derived; //ptr必须从指向Base2,指向Derived,因为析构函数的this必须是Derived
delete ptr;

//第二种情况
Derived *pder=new Derived;
pder->mumble();   //ptr必须从指向Derived到指向Based2,因为this必须是Based2

//第三种情况
Base2 *pb1=new Derived; 
Base2 *pb2=pb1->clone();//返回值必须由Derived改为Base2的子类

 

  1.thunk技术:一小段代码用来①以适当的offset来调整this指针②跳转到虚函数去

  (1)使用thunk的原因:当第二或后继的基类指针来调用派生类函数时需要修改指针的偏移值,而由于多态的真正对象必须在运行期确定,所以编译期不能确定具体的偏移量offset

是多少(类似第三章的虚基类)

Base2 *pbase2 =new Derived;
//需要改成一下代码
Derived *temp = new Derived;
Base2 *pbase2 = temp ? temp+offset:0; //offset在运行期才能知道

  2.由于不同继承链的虚函数体系不同,所以编译器给派生类的不同继承链创建不同的虚函数表(并取不同的名字)同时增加相应的指向不同虚函数表的指针。由于链接期符号名称的链接可能变得特别缓慢,所以有些编译器(sun编译器)将不同虚函数表连接在一起,只有一个名字。指向次要虚函数表的指针则由主要表格名称加上一个offset获得。

  3.编译器将第二或后继继承链的虚函数表中slot存放的内容改成存放thunk的地址(第一链是自然多态不需要调整地址)。访问相应的slot之后跳转到thunk改变this指针偏移值然后跳转到相应函数实例。

 

 

  4.“split function”技术:针对第三种情况,当函数被认为足够“小”时,sun编译器会提供一种技术:以相同算法差生两个函数,其中第二个在返回之前为指针加上必要的offset。所以通过第二或后继继承类的函数指针调用的函数会调用哪个加上offset的函数(第一继承链是自然多态指针指向地址相同)。如果函数不小,则split function策略会给于此函数中的多个进入点中的每一个。如果函数支持多重进入点,就可以不必有许多thunks。

  Microsoft用“address points”来取代thunks:把派生类中改写函数的最初引入该虚函数的类的地址作为address point。(个人理解,原书讲的不清楚)

  注:多接入点函数意思是有多种进入这个函数体的函数,本人查资料发现类似下面这种函数。鉴于goto现在可能已经不用了,所以不知道现在这个函数还有没有在使用了。

void dtor_with_delete(){
  int do_delete = 1;
  goto common_code;

void dtor_without_delete(){
  int do_delete = 0;
common_code:
  call_base_dtors_without_delete();
  do_local_dtor_stuff();
  if (do_delete)
   invoke_delete();
}

参考:https://gcc.gnu.org/legacy-ml/gcc/2000-02/msg00575.html

二、虚基类继承

  书中直接建议不要在虚基类中声明任何非静态成员。

以上是关于第四章:虚成员函数(虚函数表thunksplit function和多接入点函数)的主要内容,如果未能解决你的问题,请参考以下文章

关于虚函数,类的内存分布以及类的成员函数调用原理

虚函数

在类有成员变量的场景下, 按照虚表原理, 模拟虚函数实现

C++虚函数表小记

虚函数表存放在哪里

static函数如何调用虚函数