c++钻石继承重复继承解决方案,以及啥是vfptr和bfptr

本文详细探讨了C++中的钻石继承问题,包括重复继承导致的内存冲突和访问混乱。通过实例展示了非虚继承和虚继承在内存分配上的差异,并解释了虚继承如何解决这一问题。文章最后提到了不同继承方式下的内存占用和C#、Java等不支持多重继承的语言对此的处理差异。
摘要由CSDN通过智能技术生成

重复继承 novirtual+novirtual

在这里插入图片描述
B继承A,而C也继承A。
然后D继承B和C。这样就称为重复继承,因为看着像个菱形,所以也叫钻石继承
那么这样继承会出现什么问题呢?我们先从内存分配的角度看。
在VS使用 / d1 reportSingleClassLayoutXXX就能查看内存分配。
具体如下:
在这里插入图片描述

在这里插入图片描述
运行之后就能看到XXX类这里是VirClass类的分布情况。
在这里插入图片描述
好的,切回正题。
假设我们有个这样的代码
正如刚刚那个继承图一样:B继承A,而C也继承A。然后D继承B和C。

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	int x;
	int y;
	virtual void f1() { cout << "A:f1" << endl; };
	virtual void f2() { cout << "A:f2" << endl; };
	virtual void f3() { cout << "A:f3" << endl; };
};

class B :public A
{
public:

private:

};
class C :public A
{
public:

private:

};
class DIS :public B, public C {
public:
	virtual void f2() { cout << "B:f2" << endl; };
	virtual void f3() { cout << "B:f3" << endl; };
};
int main()
{
	return 0;
}

别问我为什么D取名为DIS,问就是 / d1 reportSingleClassLayoutXXX的机制打咩
在这里插入图片描述
此时的布局是这样的。哦,这样看是不是有点乱,没关系,我给你可视化一波。
并且可以看到class DIS size(24) 说明这个对象有24字节。那么我们来分析一下为什么有24字节
在这里插入图片描述
是的,B中会建立一个A同时C也建立了一个A。
由于A对象有虚函数,所以头部会有一个4字节的vfptr[virtual function ptr 虚函数指针]
其次是,int x,y. 8字节。
故一个A对象就有12字节。
由于B创建了一个A对象,C也创建了一个A对象,所以一共是24字节。
在这里插入图片描述

由于创建了两个A所以打咩了!!!!这直接就会造成,访问冲突。
举个栗子,

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	int x;
	int y;
	virtual void f1() { cout << "A:f1" << endl; };
	virtual void f2() { cout << "A:f2" << endl; };
	virtual void f3() { cout << "A:f3" << endl; };
};

class B :public A
{
public:
	void SetX(int x)
	{
		this->x=x;
	}
	int GetX1()
	{
		return x;
	}
private:

};
class C :public A
{
public:
	int GetX() 
	{ return x; }
private:

};
class DIS :public B, public C {
public:
	virtual void f2() { cout << "B:f2" << endl; };
	virtual void f3() { cout << "B:f3" << endl; };
};
int main()
{
	DIS* dis = new DIS();
	dis->SetX(10);
	cout<<dis->GetX()<<endl;
	cout << dis->GetX1()<<endl;
	return 0;
}


运行结果如下:
在这里插入图片描述
啊咧,明明都是访问x,但是为什么一个是0,一个是10?
正是因为这两个x其实是不同的x,一个是B的基类A的x,一个是C的基类A的x。(证明了一波刚刚的内存分配情况合理!)

好的,那么如何化解?

虚继承 virtual+virtual

将继承改成关系改成virtual

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	int x;
	int y;
	virtual void f1() { cout << "A:f1" << endl; };
	virtual void f2() { cout << "A:f2" << endl; };
	virtual void f3() { cout << "A:f3" << endl; };
};

class B :virtual public A
{
public:
	void SetX(int x)
	{
		this->x=x;
	}
	int GetX1()
	{
		return x;
	}
private:

};
class C :virtual  public A
{
public:
	int GetX() 
	{ return x; }
private:

};
class DIS : public B,public C {
public:
	virtual void f2() { cout << "B:f2" << endl; };
	virtual void f3() { cout << "B:f3" << endl; };
};
int main()
{
	DIS dis;
	dis.SetX(10);
	cout<<dis.GetX()<<endl;
	cout << dis.GetX1()<<endl;
	return 0;
}


运行结果:

在这里插入图片描述
这次x都是一样的了
再看内存分配,size(20)字节,舒服,比之前少了。
在这里插入图片描述
虚基类指针vbptr和虚函数指针vfptr

仔细看,你会发现B和C里面存放的不是A了,而是vbptr(virtual base ptr 虚继承基类指针)
这个指针指向的就是A。此时B和C共享一个A。
并且这bvptr只占4字节。然后A占12字节。
故4+4+12=20字节
在这里插入图片描述

离谱继承 novirtual+virtual

来看个最离谱的。如果C是非virtual而B是virtual会发生什么

#include<iostream>
#include<vector>
using namespace std;
class A {
public:
	int x;
	int y;
	virtual void f1() { cout << "A:f1" << endl; };
	virtual void f2() { cout << "A:f2" << endl; };
	virtual void f3() { cout << "A:f3" << endl; };
};

class B :virtual public A
{
public:
private:

};
class C :public A
{
public:
private:

};
class DIS : public B,public C {
public:
	virtual void f2() { cout << "B:f2" << endl; };
	virtual void f3() { cout << "B:f3" << endl; };
};
int main()
{
	return 0;
}


在这里插入图片描述雾草,size(28),这波是反向优化了。
好的,那么我们点根烟冷静分析这28是怎么来的。
首先如果是非virtual继承,那么会创造一个A。即B里面会创造一个A
然后如果是virtual继承,则会创建一个vbptr,然后再最外面创建一个A,使得vbptr指向A
好家伙,那就相当于有两个A,并且还多一个vbptr。打咩!
12+12+4=28
在这里插入图片描述
所以使用钻石继承的各位务必要小心使用哦。当然像C#,Java这种语言并不支持多重继承所以无需担心,而且类似接口也完全不是那么一回事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值