C++语法|多重继承详解(一)|理解虚基类和虚继承

系列汇总讲解,请移步:
C++语法|虚函数与多态详细讲解系列(包含多重继承内容)

虚基类是多重继承知识上的铺垫。

首先我们需要明确抽象类和虚基类的区别:
抽象类:有纯虚函数的类

虚基类是什么呢?请看文章!

虚基类的定义

我们定义一个类A和类B:

class A {
public:
private:
    int ma;
};
class B : public A {
public:
private:
    int mb;
};

我们可以看出,其中实例化A的对象a占4个字节;实例化B的对象b占8个字节。我们现在搞一个虚继承:

class B : virtual public A {
public:
private:
    int mb;
};

在这里,我们的类A被virtual修饰,所以A被称为虚基类。

A 和 B 的继承关系就是虚继承

此时我们的 A 还是占 4个字节;但是 B 却占了12个字节??

虚继承导致内存布局的改变?

在不定义虚继承的时候,我们如果初始化类B,应该是如下的内存分布:

A::ma
mb

这里一共是8个字节。

如果采用虚继承,内存分布如图:

虚基类的数据一定要搬到我们派生类内存的最后面,然后在0地址(相对地址)有一个 vbptr。该指针指向的是一个虚基类表 vbtable!

虚基类表是干什么的呢?由于我们把 A::ma的数据搬到派生类内存的最后了,所以当有人来找 A::ma这个数据的时候还是会在第一个地址找,那么 vbptr 就应该告诉这个人 A::ma数据在内存中的位置,所以很显然,我们的 vbtable的第二行放的就是 A::ma 数据所在的地址的一个偏移量。

那么为什么我们要这样做呢?这样做有什么意义呢?
请看本节内容菱形继承的问题及解决方法,关注菱形继承中的内存变化。

面试小测:
class A {} sizeof(A) = 1
class B {} sizeof(B) = 1


class A { virtual void fun() }
class B: public A {}
sizeof(B) = 4 因为里面多了一个vfptr


class A { virtual void fun() }
class B: virtual public A {}
sizeof(B) = 8 因为里面多了一个 vbptr 和 vfptr

Windows下虚基类导致的问题:内存泄漏

一下问题仅存在于windows vs编译器。
在Linux g++不存在该问题,g++下在释放内存时会自动完成偏移释放正确的内存地址

我们考虑这样一个情况,我们重载了类A和类B的new 和 delete函数,目的就是为了查看开辟和释放内存的具体情况:

class A {
public:
    virtual void func() { cout << "Base::func" << endl;}
    void operator delete(void *ptr) {
        cout << "operator delete p : " << ptr << endl;
        free(ptr);
    }
private:
    int ma;
};
class B : virtual public A {
public:
    void func() { cout << "call B::func" << endl;}
    void* operator new( size_t size) {
        void *p = malloc(size);
        cout << "operator new p: " << p << endl;
        return p;
    }
private:
    int mb;
};

测试函数如下:

int main () {
    A *p = new B();
    cout << "main p : " << p << endl;
    p->func();
    delete p;
    return 0;
}

我们可以看到打印结果:

operator new p: 0x013855D0
main p : 0x013855D8
call B::func
operator delete p : 013855D8

我们完整讲解一下:
首先,我们在堆内存上开辟了空间,位置在:0x137606ce0
然而,我们的p指针指向的内存地址在0x147606cf0
所以我们最后释放p指针的时候,也是释放了0x147606cf0,也就是说我们有8(不同电脑上或为16字节)个字节的内存泄漏。

这是因为,我们通过A *p = new B()的p指针指向的永远是派生类内存布局中基类的首地址,现在你因为是虚基类,所以A的地址移动到后面去了,所以P指针也移动到后面去了。

总结:
我们一定不能将虚基类的指针指向一个堆内存

代码应该这么写:

int main () 
{
	B b;
	A *p = &b; //new B();
	p->func();
	//delete p;
}
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值