【C++】菱形继承和虚继承

继承模板类(静态成员)

  1. 两个子类使用同一个类型继承,base和test由于继承object类中的静态成员变量,且都是int类型继承,因此只调用推演一次,num也只初始化一次。
template<class T>
class Object
{
private:
	T value;
public:
	Object(T x = T()) { value += 1; }
	static int num;
};
template<class T>
int Object<T>::num = 0;
class Base:public Object<int>
{
public:
	Base() { num += 1; }
	void Print() { cout << "Base:" << num << endl; }
};
class Test :public Object<int>
{
public:
	Test() { num += 1; }
	void Print() { cout << "Test:" << num << endl; }
};
int main()
{
	Base t1, b1;
	Test t2, b2;
	t1.Print();  //此时静态成员变量,存放在数据区,模板类的类型为
	                //在类外进行初始化,且进初始化一次,因此t1,b1,t2,b2操作的都是同一个num
	t2.Print();
}

如下图:
请添加图片描述
结果:
请添加图片描述

2.base用double类型继承,test用int类型继承,会推演出两个模板,自然而然,在各自的类型模板中,对静态成员变量只初始化一次

class Base:public Object<double>
{}class Test :public Object<int>
{}

请添加图片描述
结果:
请添加图片描述

菱形继承和虚继承

class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{}
};
class Base:public Object
{
	int num;
public:
	Base(int x = 0) :num(x), Object(x + 10) {}
};
class Test :public Object
{
	int sum;
public:
	Test(int x = 0):sum(x),Object(x+10){}
};
class Det :public Base, public Test
{
	int total;
public:
	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
};
int main()
{
	Det d(0);
	Base b1 = d;//ok
	Test t1 = d;//ok
	Object op = d;//error  op指向d,d继承了Base和Test,所以op不知道指向的是继承base的d还是继承了Test的d,
}

出现的问题:

在这里插入图片描述
从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Det的对象中Object成员会有两份。
请添加图片描述

解决方案:采用虚继承:

虚继承:主要是通过虚继承,在每个派生类中会有一个虚基类指针(占四个字节),和虚基类表(不占空间);当虚继承的派生类被当做基类再次虚继承,虚基类指针也会被继承。
(因此每个派生类既可以通过虚基类指针,查找虚基类表找到中的成员)

注意:

1. 一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
2. 在派生类的对象中,同名的虚基类**只产生一个虚基类子对象**,而某个非虚基类产生各自的对象。
3. 虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。
4. 在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
5. 在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
6. 虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。
//虚基类
class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{}
};
class Base:virtual public Object
{       //存在虚基类指针和虚基类表
	int num;
public:
	Base(int x = 0) :num(x), Object(x + 10) {}
};
class Test :virtual public Object
{            //存在虚基类指针和虚基类表
	int sum;
public:
	Test(int x = 0):sum(x),Object(x+10){}
};
class Det :public Base, public Test
{
	int total;
public:
//虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。  优先初始化Object
	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
};
int main()
{
	Det d(0);
	}

步骤:

  1. 调用Object的构造函数,初始化虚基类,创建隐藏基类对象,保存虚表指针和,虚表
  2. 在初始化Base,这个时候,Base由于虚继承Object,因此Base类中会有一个虚基类指针,和虚基类表。不需要在调动构造函数创建Object的创建隐藏基类对象。
  3. 初始化Test和Base相同。
  4. 对Det进行初始化,创建Det对象d
    如下图:(虚基表指针存放在数据区,存放在对象的开头)
    请添加图片描述
    sizeof(d)
    请添加图片描述
    对于Object基类,Base和Test如何找到它的?
    其实两个派生类里面并不是指针,而是记录了一个偏移量,因为在构造过程中,这一块内存就在一起,不需要拿指针指向某个位置,又不是分散在内存其他地方,书上所说的vbptr是个指针,是为了更好理解。
    根据d的地址:在base中,有一个偏移量 0x14,就是十进制的20,意味着如果要找到Object基类,需要偏移20字节。
    请添加图片描述
    同理:Test里面存放了一个偏移量 0x 0c,即十进制的12,要找到基类Base,需要偏移12个字节。
    请添加图片描述
    在菱形继承中加入虚函数。
class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{}
	virtual void fun() {}
	virtual void add() {}
};
class Base :public Object
{
	int num;
public:
	Base(int x = 0) :num(x), Object(x + 10) {}
	virtual void fun() {}
};
class Test :public Object
{
	int sum;
public:
	Test(int x = 0) :sum(x), Object(x + 10) {}
	virtual void fun() {}
	void add() {}
};
class Det :public Base, public Test
{
	int total;
public:
	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20){}
	void fun() {}
	void add() {}
};

请添加图片描述
Object基类中的虚表指针,开始指向Object中的虚表,
当程序运行到,构建Det对象,因为Det继承了Base和Test类,
用两种方式去分别继承Base和Test类,因此Object中的虚表指针,在指向Det中的虚表时,有两个,一个继承Base中的虚表,另一个是继承test中的虚表
请添加图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++菱形继承是指一个派生类同时继承了两个直接或间接基类,而这两个基类又间接或直接继承自同一个基类,从而形成了一个菱形的继承关系。 例如下面的代码: ``` class A { public: int a; }; class B : public A { public: int b; }; class C : public A { public: int c; }; class D : public B, public C { public: int d; }; ``` 在这个例子中,类 `D` 继承了类 `B` 和类 `C`,而类 `B` 和类 `C` 都继承了类 `A`,因此形成了一个菱形继承关系。 菱形继承会引起一些问题,例如: 1. 内存浪费:由于类 `A` 被重复继承,导致在内存中存在两份相同的 `A` 对象,造成内存浪费。 2. 访问冲突:由于类 `D` 继承了类 `B` 和类 `C`,而这两个类都继承了类 `A`,因此在类 `D` 中访问 `A` 中的成员时会出现访问冲突的问题。 为了解决菱形继承带来的问题,可以使用虚继承虚继承可以解决内存浪费和访问冲突的问题,它的原理是在派生类中只保留一个基类的实例,由所有的派生类共享使用。 修改上面的例子,使用虚继承: ``` class A { public: 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; }; ``` 在这个例子中,类 `B` 和类 `C` 继承类 `A` 时使用了 `virtual` 关键字,表示使用虚继承。这样,类 `D` 中就只有一个 `A` 对象的实例,而且访问 `A` 中的成员也不会出现访问冲突的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值