探索: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++多继承中的菱形继承,是多么的让人费解!也正因如此,我们不必要时,尽量避免写出菱形虚拟继承这样的结构出来,这对大家都好!

  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值