序 :以下测试和结论都是以单继承的基础。

问题1:虚表是什么,存在哪里,与类和其实例化对象有啥关联

            众所周知,虚表是一个 虚表实际是一个个函数指针的集合,该集合是个数组  。这个可以从各种资料上得到的书面解释。那它存在哪里,与对象、类是什么关系。今天我们从代码层面上来一探究竟。

//第一个示例,定义个含有虚函数的类型,并用之示例化

class baseClass {
public:
    virtual void fun1() {
        std::cout << "base::fun1()" << std::endl;
    }
    virtual void fun2() {
        std::cout << "base::fun2()" << std::endl;
    }
    void fun3() {
        std::cout << "base::fun3() non-virtual" << std::endl;
    }

protected:
    int _b = 0;
};

int main()
{
    baseClass base1;
    baseClass base2;

   int* pVFT_base2 =   ((int*)&base2);

    getchar();
    return 0;
}

运行后监视两个实例化的对象,及base2首地址的内容

再谈多态基于虚表实现原理

    从图中明确看出 base1 base2 两个对象拥有了一样的虚表,也即两个对象同用了同一张虚表,而pVFT_base2定义的指针是base2的首地址,其里面的内容为虚表的指针的地址。

如此得出结论:

1、虚表一个函数指针的数组。

2、含虚函数的类的首个4字节的地址存的是一个指针,该指针内容及为虚表的起始地址,这一点铺垫了后面的测试。
3、 一个类会持有一个虚表,与实例化对象无关,同一个类的所有实例化的对象持有的虚表是同一个,和静态成员变量类似。

问题二:多态基于继承关系中虚表实现,那虚表在继承关系中是如何维护的。

在上面的例子里,添加个派生类,对父类不做任何动作,并也用之实例化一对象,代码如下

class baseClass {
public:
    virtual void fun1() {
        std::cout << "base::fun1()" << std::endl;
    }
    virtual void fun2() {
        std::cout << "base::fun2()" << std::endl;
    }
    void fun3() {
        std::cout << "base::fun3() non-virtual" << std::endl;
    }

protected:
    int _b = 0;
};

class deriveClass : public baseClass {
public:

    virtual void fun5() {
        std::cout << "deriveClass::fun5()" << std::endl;
    }

protected:
    int _d = 0;
};

 

int main()
{
    baseClass base1;
    baseClass base2;
    deriveClass derive;

   int* pVFT_base2 =   ((int*)&base2);

    getchar();
    return 0;
}

再讲 示例话的derive加入的监视中,一探究竟。

再谈多态基于虚表实现原理

现在在 deriveClass重载func2接口,再看一遍监视。

再谈多态基于虚表实现原理

从两张图中对比可知。含有虚函数的类被继承后会有如下几个动作

1、派生类会从其父类(为啥是父类,而不是基类),拷贝其全部的虚表元素到自己的虚表里面

2、如果派生类重载 了接口,则会将自己虚表里面对应偏移的接口地址改写成重载后的接口地址


为验证是拷贝的父类还是基类,再加层继承,从deriveClass 派生出ThirsDeriveClass,不做任何实际动作,再实例化一对象。看监视器。

再谈多态基于虚表实现原理

图中看出 fun2 是其父类的地址,而非基类的。上面的结论正确。但是奇怪的问题来了,fun5 也为虚函数,为啥没在虚表里面展示出来。难道还有另一张虚表存在么,这个猜想肯定是错的,一个类只有一个虚表,那真相是没展示出来。单func5确确实实存在在虚表里面,而且在虚表中的位置应该紧随继承而来的虚函数后面。下面我们来模拟下虚函数的调用,看看能不能通过虚表的偏移将func5调用起来。。来验证我们的猜想


#include "stdafx.h"
#include "iostream"

class baseClass {
public:
    virtual void fun1() {
        std::cout << "base::fun1()" << std::endl;
    }
    virtual void fun2() {
        std::cout << "base::fun2()" << std::endl;
    }
    void fun3() {
        std::cout << "base::fun3() non-virtual" << std::endl;
    }

protected:
    int _b = 0;
};

class deriveClass : public baseClass {
public:
    virtual void fun2() {
        std::cout << "deriveClass::fun2()" << std::endl;
    }
   virtual  void fun5() {
        std::cout << "deriveClass::fun5()" << std::endl;
    }
protected:
    int _d = 0;
};

class ThirsDeriveClass : public  deriveClass {
public:
    virtual  void fun5() {
        std::cout << "ThirsDeriveClass::fun5()" << std::endl;
    }
};

typedef void (__stdcall *PVFT)();  //函数指针
void ImitateCall(baseClass& class1) {
    PVFT* pVFT = (PVFT*)(*((int*)&class1));
    while (*pVFT) {
        (*pVFT)();
        pVFT += 1;
    }
    std::cout << std::endl;
}

int main() {
    baseClass base1;
    baseClass base2;
    deriveClass derive;
    ThirsDeriveClass  ThirsDerive;

    int* pVFT_base2 = ((int*)&base2);

    std::cout << "base call begin" << std::endl;
    ImitateCall(base1);

    std::cout << "ThirsDerive call begin" << std::endl;
    ImitateCall(ThirsDerive);

    std::cout << "derive call begin" << std::endl;
    ImitateCall(derive);
    getchar();
    return 0;
}

再谈多态基于虚表实现原理

运行的结果如果,确实可以通过编译调用到派生类自己的虚函数,故此印证了我们的猜测是正确的。

相关文章: