C++学习之路-菱形继承与虚继承

C++学习之路-菱形继承与虚继承,第1张

菱形继承与虚继承
  • 菱形继承
    • 菱形继承定义
    • 菱形继承的问题
  • 虚继承
  • 虚继承实现的原理
    • 虚继承的意义

菱形继承 菱形继承定义

继承的形状类似于菱形,比如爷爷类有一个,父类有2个 子类有一个。如下图所示:

即 Undergraduate 继承于Student,Worker。Student,Worker又同时继承于Person。

class Person
{
	int m_age;
public:
	Person() {}

};

class Student : public Person
{
	int m_score;
public:
	Student() {}

};

class Worker : public Person
{
	int m_salay;
public:
	Worker() {}

};

class Undergraduate : public Student, public Worker
{
	int m_grade;
public:
	Undergraduate() {}

};
菱形继承的问题

我们知道Student 继承于Person,那么就相当于把Person里所有的东西都拿到Student里。Person的成员变量也会存于Student里

class Student : public Person
{
	int m_age;
	int m_score;
public:
	Student() {}
};

同理,Worker类也是如此,会将Person类的成员变量存于Worker类。

class Worker : public Person
{
	int m_age;
	int m_salay;
public:
	Worker() {}

};

由于,Undergraduate 类多继承于Student类 和Worker 类,也会讲成员继承下来,那么Undergraduate 类就会出现这样的景象:

class Undergraduate : public Student, public Worker
{
	int m_age; 	//从Student里继承的
	int m_score;

	int m_age;	//从Worker里继承的
	int m_salay;
	
	int m_grade;
public:
	Undergraduate() {}
};

问题来了,菱形继承的问题:最底下的子类从基类继承的成员变量会冗余、重复。同时会产二义性,进而报错:m_age不明确

(这里我把所有的成员变量都设置成public了,所以可以直接访问)

因为Student和Worker都继承了Person的m_age,所以Undergraduate会继承两遍m_age。但是这种重复的继承,并不是我们想要的。我们想要的是 Undergraduate只继承一遍m_age。

怎么解决这个问题呢?有一个方法叫做:虚继承

虚继承

虚继承的出现,就是为了解决菱形继承中成员重复继承的问题。解决办法就是虚继承,让中间的两个类虚继承基类,继承同一个成员变量,如下图所示:

只要,Student和worker虚继承Person,就可以解决Undergraduate类中,重复继承的问题。其中被虚继承的类Person 叫做虚基类。

// Person类叫做虚基类
class Person
{
public:
	int m_age;
	Person() {}
};

class Student : virtual public Person
{
public:
	int m_score;
	Student() {}
};

class Worker : virtual public Person
{
public:
	int m_salay;
	Worker() {}
};

class Undergraduate : public Student, public Worker
{
public:
	int m_grade;
	Undergraduate() {}
};

虚继承之后,我再访问m_age,就不会产生二义性了。

虚继承的语法很简单:只需在继承前加上virtual关键字即可。

class Student : virtual public Person
{
};
class Worker : virtual public Person
{
};
虚继承实现的原理

虚继承的作用就是让Student和Worker 共享相同的基类成员。

假如没用虚继承,一个Student对象里都有什么呢?

class Person
{
public:
	int m_age;
	Person() {}
};

class Student : public Person
{
public:
	int m_score;
	Student() {}
};

很显然,存放着两个成员变量

那么,虚继承之后,Student对象里有什么变化呢?

class Person
{
public:
	int m_age;
	Person() {}
};

class Student : virtual public Person
{
public:
	int m_score;
	Student() {}
};

一旦,Student类虚继承于父类,那么Student创建的对象的内存里就会多出一个东西:虚表指针。

虚表指针指向的0和8都代表什么意思呢?

0:虚表指针与本类起始地址的偏移量,一般是0。因为虚表指针存放在本类的开头,所以偏移量是0

8:我们可以看到,m_age在Student对象内存中,竟然排在了m_score后面。我们知道,子类继承父类,都会将父类的成员变量存于对象内存的前面,起码也是子类自己成员变量的前面。虚继承就会有这样的特点:讲虚基类中的成员变量放在本类成员变量的后面。好了,现在解释8是什么意思:虚基类第一个成员变量与本类其实地址的偏移量。这个数字受什么影响呢?我们根据原理也知道,受本类成员变量数量的影响,有一个成员变量,再加上虚表指针占的字节数,就是8。有两个成员变量,加上虚表指针,就是12。以此类推…

Worker类也是如此,只要虚继承了基类。

那我们此时看看Undergraduate对象里都有啥。如下图所示:

Student类里的虚表指针是0和20,0代表虚表指针与Student类起始偏移量;20代表m_age与Student类起始地址的偏移量。

Worker类里的虚表指针式0和12,0代表虚表指针与Worker类起始偏移量;12代表m_age与Worker类起始地址的偏移量。

注意图中的本类起始地址,是虚表指针所在的类,而不是Undergraduate类。

虚表指针里,存放20和12这种偏移量有啥用呢?这就是虚继承实现的原理:通过虚表指针里第二个偏移量(12和20)来定位虚基类中的成员变量

虚继承的意义

我们通过虚继承,是想要最下面的派生类不要重复的继承最上面基类中的成员变量。一旦虚继承之后,最下面的派生类Undergraduate,反而对象内存增加。这样的意义在哪呢?

  • 首先,不采用虚继承,当调用基类中的成员变量时会产生二义性,会报错。

  • 其次,最下面的派生类Undergraduate内存现在看着是增大了。如果,基类中有20个成员变量。不采用虚继承,就会继承220=40个成员变量,采用虚继承只需要继承120+2=22个,其中2个是虚表指针,所以长远考虑,虚继承的意义还是很大的。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/1324155.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存