一、虚函数表解析

  • 虚函数(Virtual Function)是通过​一张虚函数表(Virtual Table)来实现的​。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖(重写)的问题
  • 在有虚函数的类中,虚函数表被分配在类实例的内存中,所以当用父类的指针来操作一个子类的时候,这张虚函数表就显得非常重要了
  • 虚函数表类似于一张地图,​指明了实际所应该调用的函数
  • C++标准说明书中说到,​编译器必须保证虚函数表的指针存在于对象实例中最前面的位置​(这是为了保证正确取到虚函数的偏移量)。这意味着通过对象实例的地址得到这张虚函数表,之后就可以遍历其中的函数指针,并调用相应的函数
  • 关于虚函数表的设计还可以参阅《C++对象模型》

二、无继承下的虚函数表解析

#include <iostream>

using namespace std;

class Base
{
public:
virtual void func1() { cout << "Base::fun1" << endl;}
virtual void func2() { cout << "Base::fun2" << endl;}
virtual void func3() { cout << "Base::fun3" << endl;}
private:
int num1;
int num2;
};

// 函数指针
typedef void (*Fun)();

int main()
{

Base b;

Fun pFun;

//取得Base::func1()的地址
pFun = (Fun)*((int*)*(int*)(&b) + 0);
pFun();

//取得Base::func2()的地址
pFun = (Fun)*((int*)*(int*)(&b) + 1);
pFun();

//取得Base::func3()的地址
pFun = (Fun)*((int*)*(int*)(&b) + 2);
pFun();

return 0;
}
  • 上面的代码可能编译不通过,由于编译器的原因
  • 转换解析:
(Fun)*((int*)*(int*)(&b) + 0);

// 取得对象b的地址,转换为int*, 又因为虚函数表在类的其实位置, 因此这一步也是取得虚函数表的地址
(int*)(&b);

// 偏移都虚函数表的第一个函数处
(int*)(&b) + 0;

// 取得第一个函数的地址
*(int*)(&b) + 0;

// 将第一个函数的地址转换为int*
(int*)*(int*)(&b) + 0;

// 取得地址中的函数
*((int*)*(int*)(&b) + 0);

// 转换为Fun函数
(Fun)*((int*)*(int*)(&b) + 0);
  • Base类的内存结构如下所示:
  • vfptr:虚函数表指针
  • num1、num2:数据成员

C++:87---类继承(虚函数表:Virtual Table)_虚函数表

三、单一继承(无重写(覆盖))

  • 下面是单一继承中虚函数表的设计,​并且派生类没有重写(覆盖)基类的虚函数
#include <iostream>

using namespace std;

class Base1
{
public:
Base1(int num) : num_1(num) { }
virtual void foo1() { cout << "Base1::foo1" << endl;}
virtual void foo2() { cout << "Base1::foo2" << endl;}
virtual void foo3() { cout << "Base1::foo3" << endl;}
private:
int num_1;
};

class Derived1 : public Base1
{
public:
Derived1(int num) : Base1(num) { }
virtual void faa1() { cout << "Derived1::faa1" << endl;}
virtual void faa2() { cout << "Derived1::faa2" << endl;}
};
  • Derived1继承自Base1类,其虚函数表如下所示:
  • 前面3个为基类Base1的虚函数
  • 后面两个为Derived1自己的虚函数

C++:87---类继承(虚函数表:Virtual Table)_派生类_02

四、单一继承(有重写(覆盖))

  • 下面是单一继承中虚函数表的设计,​并且派生类重写(覆盖)了基类的虚函数
#include <iostream>

using namespace std;

class Base1
{
public:
Base1(int num) : num_1(num) { }
virtual void foo1() { cout << "Base1::foo1" << endl;}
virtual void foo2() { cout << "Base1::foo2" << endl;}
virtual void foo3() { cout << "Base1::foo3" << endl;}
private:
int num_1;
};

class Derived2 : public Base1
{
public:
Derived2(int num) : Base1(num) { }
virtual void foo2() { cout << "Derived2::foo2" << endl;} //重写了基类的foo2虚函数
virtual void fbb2() { cout << "Derived2::fbb2" << endl;}
virtual void fbb3() { cout << "Derived2::fbb3" << endl;}
};
  • Derived2继承自Base1类,其虚函数表如下所示:
  • 第1个、第3个为基类Base1的虚函数
  • 第2个本来是基类Base1的虚函数,由于其重写了基类的foo2()虚函数,因此需要更换为自己的虚函数指针
  • 后面两个为Derived2自己的虚函数

C++:87---类继承(虚函数表:Virtual Table)_虚函数_03

  • 当我们通过基类指针指向于派生类时,调用其中的函数就是根据这张虚函数表来调用的
Derived2 *p = new Base1(1);

p->foo2(); //调用派生类的foo2()函数

五、多重继承(无重写(覆盖))

  • 下面是多重承中虚函数表的设计,​并且派生类没有重写(覆盖)基类的虚函数
#include <iostream>

using namespace std;

class Base1
{
public:
Base1(int num) : num_1(num) { }
virtual void foo1() { cout << "Base1::foo1" << endl;}
virtual void foo2() { cout << "Base1::foo2" << endl;}
virtual void foo3() { cout << "Base1::foo3" << endl;}
private:
int num_1;
};

class Base2
{
public:
Base2(int num) : num_2(num) { }
virtual void foo1() { cout << "Base2::foo1" << endl;}
virtual void foo2() { cout << "Base2::foo2" << endl;}
virtual void foo3() { cout << "Base2::foo3" << endl;}
private:
int num_2;
};

class Base3
{
public:
Base3(int num) : num_3(num) { }
virtual void foo1() { cout << "Base3::foo1" << endl;}
virtual void foo2() { cout << "Base3::foo2" << endl;}
virtual void foo3() { cout << "Base3::foo3" << endl;}
private:
int num_3;
};

class Derived3 : public Base1, public Base2, public Base3
{
public:
Derived3(int num1, int num2, int num3) : Base1(num1), Base2(num2), Base3(num3) { }
virtual void fcc1() { cout << "Derived3::fcc1" << endl;}
virtual void fcc2() { cout << "Derived3::fcc2" << endl;}
};
  • Derived2继承自Base1、Base2、Base3类,其虚函数表如下所示:
  • Derived继承3个基类,其中为每一个基类都涉及了一张虚函数表

C++:87---类继承(虚函数表:Virtual Table)_派生类_04

  • 为每一个基类都涉及了一张虚函数表的目的是:​为了解决不同的父类类型的指针指向同一个子类实例,而能够调用实际的函数​。例如:
Base2 *pBase2 = new Derived3();
pBase2->foo2(); //调用Base2::foo2()

六、多重继承(有重写(覆盖))

  • 下面是多重承中虚函数表的设计,​并且派生类重写(覆盖)了基类的虚函数
#include <iostream>

using namespace std;

class Base1
{
public:
Base1(int num) : num_1(num) { }
virtual void foo1() { cout << "Base1::foo1" << endl;}
virtual void foo2() { cout << "Base1::foo2" << endl;}
virtual void foo3() { cout << "Base1::foo3" << endl;}
private:
int num_1;
};

class Base2
{
public:
Base2(int num) : num_2(num) { }
virtual void foo1() { cout << "Base2::foo1" << endl;}
virtual void foo2() { cout << "Base2::foo2" << endl;}
virtual void foo3() { cout << "Base2::foo3" << endl;}
private:
int num_2;
};

class Base3
{
public:
Base3(int num) : num_3(num) { }
virtual void foo1() { cout << "Base3::foo1" << endl;}
virtual void foo2() { cout << "Base3::foo2" << endl;}
virtual void foo3() { cout << "Base3::foo3" << endl;}
private:
int num_3;
};

class Derived4 : public Base1, public Base2, public Base3
{
public:
Derived4(int num1, int num2, int num3) : Base1(num1), Base2(num2), Base3(num3) { }
// 重写了Base1::foo1()、Base2::foo1()、Base3::foo1()
virtual void foo1() { cout << "Derived4::foo1" << endl;}
virtual void fdd() { cout << "Derived4::fdd" << endl;}
};
  • Derived2继承自Base1、Base2、Base3类,其虚函数表如下所示:
  • Derived继承3个基类,其中为每一个基类都涉及了一张虚函数表
  • 并且将重写的虚函数替换为自己的虚函数指针

C++:87---类继承(虚函数表:Virtual Table)_虚函数表_05