系列汇总讲解,请移步:
C++语法|虚函数与多态详细讲解系列(包含多重继承内容)
虚基类是多重继承知识上的铺垫。
首先我们需要明确抽象类和虚基类的区别:
抽象类:有纯虚函数的类
虚基类是什么呢?请看文章!
文章目录
- 虚基类的定义
- 虚继承导致内存布局的改变?
- Windows下虚基类导致的问题:内存泄漏
虚基类的定义
我们定义一个类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;
}