C++虚函数表解析

前言
静态类型:对象声明的类型为静态类型
动态类型:对象所指的类型为动态类型

静态绑定和动态绑定:绑定的是对象的静态类型还是动态类型,virtual函数都是动态绑定,其他的都是静态绑定,包括virtual函数中的默认形参。

虚函数
C++中的虚函数主要是实现了多态机制。关于多态,就是用父类型的指针指向子类型的实例,然后通过父类型的指针调用子类型的函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,就是试图使用不变的代码来实现可变的算法。

虚函数表
虚函数(Virtual Function)是通过一张虚函数(Virtual Table)表来实现的。在这个表中,主要是一个类的虚函数的地址,这张表解决了继承、覆盖的问题,保证其内容反应的是实际的函数。虚函数表被所有类的实例共享。所以当我们用父类的指针操作一个子类的时候,这张虚函数表就像地图一样,指明了实际所应该调用的函数。

C++编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置,这意味着我们可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中的函数指针了。假设我们有这样一个类:
  class Base{
  public:
    virtual void f(){cout<<"Base::f"<<endl;}
    virtual void g(){cout<<"Base::f"<<endl;}
    virtual void h(){cout<<"Base::f"<<endl;}
  };

按照上面的说法,我们就可以通过Base的实例来得到虚函数表,如下:
  typedef void (*Fun)();
  Base b;
  Fun pFun = NULL;
  cout<<"虚函数表的地址:"<<(int *)(&b)<<endl;
  cout<<"虚函数表中第一个函数地址:"<<(int *)*(int *)(&b)<<endl;

  //Invoke the first virtual function
  pFun = (Fun)*((int *)*(int *)(&b));
  pFun;

实际运行结果为:
  虚函数表的地址:0012FED4
  虚函数表中第一个函数地址:0044F148
  Base::f

通过这个实例可以看出,我们可以强行把&b转成int *,取得虚函数表的地址,然后再次取地址可以得到第一个虚函数的地址了,也就是Base::f()。通过这个实例,我们也知道调用Base::g()和Base::h()的方法:
  (Fun)*((int *)*(int *)(&b) + 0); //Base::f()
  (Fun)*((int *)*(int *)(&b) + 1); //Base::g()
  (Fun)*((int *)*(int *)(&b) + 2); //Base::h()

如果代码看起来太乱,没问题,让我画个图解释一下:
    这里写图片描述
图片中的.是结束符的标志,类似于字符串的’\0’的结束符。这在不同的系统中是不一样的。

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的,这样将主要是为了对比。

一般继承(无覆盖)
假设有如下所示的一个继承关系:
    这里写图片描述

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在子类的实例中,其虚函数表如下所示:
    这里写图片描述

我们可以看到以下几点:
  1) 虚函数按照其声明顺序放在虚函数表中
  2) 父类的虚函数在子类的虚函数前

一般继承(有覆盖)
覆盖父类的虚函数是很显然的事情,否则,虚函数将毫无意义。假设有下面的继承关系:
    这里写图片描述

在这个类的设计中,子类只覆盖父类的一个函数:f()。那么,对于子类的虚函数表会是一个什么样子的呢?
    这里写图片描述

从表中可以看出一下几点:
  1) 覆盖的f()函数被放到了原来父类函数的位置
  2) 没有被覆盖的函数依旧

这样,我们就可以看到对于下面这样的程序:
  Base *b = new Derived();
  b->fun();
由b所指的内存中,虚函数表的f()的位置已经被Derived::f()函数所取代,于是在实际调用关系发生时,是Derived::f()函数被调用了。

多重继承(无覆盖)
下面,再来看看多重继承的情况,假设有如下的继承关系:
    这里写图片描述

对于其子类的虚函数表如下所示:
    这里写图片描述

我们可以看出:
  1) 每个父类都有自己的虚函数表
  2) 子类的成员被放到第一个父类的虚函数表中

多重继承(有覆盖)
下面在来看看,多重继承有函数覆盖的情况:
    这里写图片描述

对于子类的虚函数表情况如下:
    这里写图片描述

我们可以看到,三个父类函数表中的f()的位置都被替换成了子类的f()函数指针,这样,任一静态类型的父类来指向子类,并调用f(),都是调用的子类的f()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值