菱形继承和虚继承

本文介绍了单继承和多继承的概念,重点讨论了菱形继承带来的数据冗余和二义性问题,并阐述了虚继承如何通过使用偏移量解决这些问题。然而,虚继承虽然减少了冗余,但也带来了性能损耗。最后,提到了友元关系不继承以及静态成员在继承体系中的特性。

一、单继承和多继承

  • 单继承:一个子类只有一个直接父类时,称这个继承为单继承。
  • 多继承:一个子类有俩个或以上的直接父类时称这个继承关系为多继承。

单继承:可以有多个父类,但是只能由一个直接父类。示意图如下:

多继承示意图如下:

菱形继承示意图如下:

菱形继承对象模型:Assistant的对象中有两份Person成员,菱形继承存在数据冗余和二义性的问题。

菱形继承举例:

class person
{
public:
	string _name;
};
 
class student : public person
{
protected:
	int _num;
};
 
class teather :public person
{
protected:
	int _id;
};
 
class Assistant :public student, public teather
{
protected:
	string _majorCourse;
};
 
void Test()
{
	Assistant a;
	//a._name = "xxx";    //对_name的访问不明确,要指定他们在哪个类里
	a.student::_name = "xxx";
	a.teather::_name = "yyy";
}

这个编译是可以通过的,但是我们发现这样的话,一个人就具有了俩个名字,这样的菱形继承是被人诟病的。说明我们的菱形继承存在二义性和数据冗余问题。

三、虚继承(virtual)——解决菱形继承的二义性和数据冗余的问题

  • 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和浪费空间的问题。
  • 虚继承体系在实际应用中,我们通常不会定义这么复杂的继承体系。一般不要定义菱形结构的虚继承体系,因为使用虚继承解决数据冗余问题的同时也带来了性能上的损耗。

菱形虚拟继承对象模型:图中解决二义性时在vs下使用的是偏移量。

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;
};
 
void Test()
{
	D dd;
	cout << sizeof(dd) << endl;   //24
	dd.B::_a = 1;
	dd._b = 3;
	dd.C::_a = 2;
	dd._c = 4;
	dd._d = 5;
 
}

 虚继承到底做了什么?

虚继承之所以能解决二义性和数据冗余的问题,他们在原本需要存放数据的地方存放了一个地址,而真正的“_a”数据放在对象的最底下,而我们只要通过这个地址里的内容——偏移量,就能找到我们实际要访问的变量“_a”。

NOTE:

友元关系不能继承,也就是说友元不能访问子类私有和保护成员。

父类定义了static成员,则整个继承体系里面只要一个这样的成员,无论子类生出多少个子类,都只有一个static成员实例。

 

C++中的虚继承最广为人知的作用是解决菱形继承问题,但这并不意味着其意义仅限于此。虚继承的核心机制在于确保一个类在继承层次结构中无论被继承多少次,都只保留一个实例。这种机制除了在菱形继承中发挥重要作用外,在其他一些场景中也能提供特定的优势。 ### 1. 虚继承的扩展意义 虚继承通过引入虚基类(virtual base class)的概念,使得继承层次中某些类的实例可以共享。这种共享机制不仅限于解决菱形继承问题,还能够被利用在一些特定的设计模式复杂继承结构中,确保某些资源或状态在整个继承体系中保持唯一性。 例如,当多个中间类需要访问某个共同父类的状态,但这些中间类之间没有直接的菱形继承关系时,虚继承仍然可以确保父类的唯一性,从而避免不必要的资源冗余[^3]。 ### 2. 与多继承的兼容性 即使在没有形成菱形继承的情况下,虚继承也可以提升多继承设计的灵活性。在某些设计中,虽然当前的继承关系未形成菱形结构,但未来可能扩展出多个派生路径共享同一个基类。在这种情况下,提前使用虚继承可以为未来的扩展提供便利,避免后期重构的麻烦[^2]。 ### 3. 降低二义性风险 虚继承的作用不仅仅是解决已有的二义性问题,还包括预防潜在的二义性。在某些复杂的继承体系中,即便没有形成菱形结构,也可能因为多个派生类分别继承了相同的基类而导致访问成员时出现歧义。使用虚继承可以在一定程度上避免这些潜在的冲突,提高代码的健壮性[^5]。 ### 4. 对对象布局性能的影响 虚继承的实现通常涉及额外的间接寻址机制来访问虚基类的成员,这会带来一定的运行时开销。然而,这种开销在某些应用场景中是值得的,尤其是当需要确保基类实例唯一性时。例如,在大型系统中,虚继承可以减少内存占用,避免重复的基类实例造成的资源浪费[^1]。 ### 示例代码 以下代码展示了虚继承在非菱形继承结构中的使用: ```cpp #include <iostream> using namespace std; class A { public: int value = 10; }; class B : virtual public A {}; // 虚继承 class C : virtual public A {}; // 虚继承 class D : public B, public C {}; int main() { D d; cout << d.value << endl; // 输出 10,只有一个 A 实例 return 0; } ``` 在这个例子中,虽然类 D 的继承结构并未形成菱形继承,但由于类 B C 都使用了虚继承,类 A 的实例在整个继承体系中仍然保持唯一性。 ### 总结 虚继承的意义不仅限于解决菱形继承问题,它还可以用于预防潜在的二义性、提高多继承设计的灵活性,以及优化资源占用。尽管虚继承会带来一定的性能开销,但在特定场景下,其优势远大于代价。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值