探索:C++继承中虚表与虚基表的内存存储

探讨:菱形虚拟继承的虚基表和虚表

在继承和多态里,总是能听到虚表、虚基表这样的词汇,没有洞悉其根本的人很容易将它们混淆,因此,我们对这两个“虚”“表”进行实践,来更好地理解它们。

通俗些说:
虚基表,即虚基类表,存放的是偏移量,是该表的位置与基类那一部分内容的地址之间的距离。
虚表:即虚函数表,存放的是函数指针,是某个类中所有虚函数的指针的集合。

  1. 初始&菱形虚拟继承

    有这么一段菱形虚拟继承的代码:

    class A 
    { 
    public: 
    	void func1() {
    		cout << "A::func1()" << endl;
    	} 
    	int a;
    }; 
    class B : virtual public A 
    { 
    public: 
    	int b;
    };
    class C : virtual public A
    {
    public:
    	int c;
    };
    class D : public B, public C
    {
    public:
    	int d;
    };
    
    int main()
    {
    	D d1;
    	d1.a = 1;
    	d1.b = 2;
    	d1.c = 3;
    	d1.d = 4;
    	return 0;
    }
    

    这是一个典型的菱形虚拟继承,我们通过调试,来看看它在内存中的布局:

    在这里插入图片描述

    因为没有虚函数,所以没有虚函数表(虚表)。
    而虚基类表(虚基表)来自于:菱形虚拟继承(虚拟:解决菱形继承的二义性和冗余)

  2. 重写/覆盖

    class A 
    { 
    public: 
    	virtual void func1() 
    	{
    		cout << "A::func1()" << endl;
    	} 
    	int a;
    }; 
    class B : virtual public A 
    { 
    public: 
    	void func1() 
    	{
    		cout << "B::func1()" << endl;
    	} 
    	int b;
    };
    class C : virtual public A
    {
    public:
    	int c;
    };
    class D : public B, public C
    {
    public:
    	int d;
    };
    
    int main()
    {
    	D d1;
    	d1.a = 1;
    	d1.b = 2;
    	d1.c = 3;
    	d1.d = 4;
    	return 0;
    }
    

    此时B中的func1()与A中的func1()构成重写(覆盖),整个过程请参看下面的调试过程:

    在这里插入图片描述

    刚才仅仅是在B中重写,那如果C中也进行重写呢?此时到底是重写B的实现,还是C的实现呢?
    答案是:都不是!
    如果B和C同时对A的func1()进行重写,编译器会报错,因为不明确!此时只需要在D中,也进行func1()的重写,那么就OK了!编译器会把D的实现重写进去,不会写B和C的实现。(当然B和C的重写并非没用,当有些场景创建“B b1;”B类型的对象b1时,就可以用到B的重写了)

  3. 中间两个派生类有自己的虚函数

    刚才的代码都是基类A的虚函数,如果派生类自己也有虚函数呢?请看:

    class A 
    { 
    public: 
    	virtual void func1() 
    	{
    		cout << "A::func1()" << endl;
    	} 
    	int a;
    }; 
    class B : virtual public A 
    { 
    public: 
    	void func1() 
    	{
    		cout << "B::func1()" << endl;
    	} 
        virtual void funcB()
        {
            cout << "B::funcB()" << endl;
        }
    	int b;
    };
    class C : virtual public A
    {
    public:
        void func1() 
    	{
    		cout << "C::func1()" << endl;
    	} 
        virtual void funcC()
        {
            cout << "C::funcC()" << endl;
        }
    	int c;
    };
    class D : public B, public C
    {
    public:
        void func1() 
    	{
    		cout << "D::func1()" << endl;
    	} 
    	int d;
    };
    
    int main()
    {
    	D d1;
    	d1.a = 1;
    	d1.b = 2;
    	d1.c = 3;
    	d1.d = 4;
    	return 0;
    }
    

    两个中间的派生类B和C,分别加上自己的虚函数funcB()funcC(),此时就没有刚才那么简单了,请看下图:

    在这里插入图片描述

    在原先的基础上,B和C分别多了一个虚函数表,存放它们自己的虚函数的地址。

  4. 最后一个派生类有自己的虚函数

    刚才是中间两个派生类有自己的虚函数,它们会产生自己的虚函数表,那么如果最后一个派生类(多继承的那个)也有自己的虚函数呢?你肯定以为它也会产生一个自己的虚函数表,但是你错了。请看下面的调试图:

    在这里插入图片描述

    至此可以窥得,C++多继承中的菱形继承,是多么的让人费解!也正因如此,我们不必要时,尽量避免写出菱形虚拟继承这样的结构出来,这对大家都好!

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-13 15:52:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-13 15:52:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-13 15:52:03       87 阅读
  4. Python语言-面向对象

    2024-03-13 15:52:03       96 阅读

热门阅读

  1. HTTP协议相关面试知识

    2024-03-13 15:52:03       42 阅读
  2. MongoDB学习笔记

    2024-03-13 15:52:03       41 阅读
  3. 【项目实践】Pyside6+Qtdesigner:登录窗体设计

    2024-03-13 15:52:03       37 阅读
  4. SQL 中避免使用 != 或 <>

    2024-03-13 15:52:03       38 阅读
  5. unityAB包管理(远程下载)

    2024-03-13 15:52:03       37 阅读
  6. 【蓝桥杯】分糖果

    2024-03-13 15:52:03       42 阅读
  7. 前端框架发展史

    2024-03-13 15:52:03       41 阅读