菱形继承问题和虚继承
0x01 菱形继承
假设有类B和类C,它们都继承了相同的类A。另外还有类D,类D通过多重继承机制继承了类B和类C。
如果直接继承会引发访问不明确(二义性),以及数据冗余。如果直接指定访问对象,可解决二义性,而要解决数据冗余,则要引入虚函数。
因为图表的形状类似于菱形(或者钻石),因此这个问题被形象地称为菱形问题(钻石继承问题)。
示例代码:
#include <Windows.h> #include <iostream> using namespace std; class Life { public: Life() :LifeMeaning(5) { } public: int LifeMeaning; }; class Bird :public Life { public: Bird() :BirdMeaning(0x50) { } public: int BirdMeaning; }; class Human :public Life { public: Human() :HumanMeaning(0x100) { } public: int HumanMeaning; }; class Angel :public Bird, public Human { public: Angel() :AngelMeaning(0x30) { } public: int AngelMeaning; }; int main() { Angel Angel; return 0; }
内存窗口观察Angel对象的基地址,可以看到有两个05(Life中的成员变量LifeMeaning的值05),这是因为子类对象会包父类的成员变量。对于Bird和Human来说,都会去包含Life类中LifeMeaning的值05。对于天使Angel来说,会同时包含Bird和Human的所有成员。故而LifeMeaning的这个变量在子类Angel中出现了两次,这是菱形继承问题。
对于二义性,可以通过作用域符指定访问对象来消除(Angel.Bird::LifeMeaning),而数据冗余的问题,则要通过虚继承。
0x02 虚继承
实例代码:
#include <Windows.h> #include <iostream> using namespace std; class Life { public: Life() :LifeMeaning(0x5) { } public: int LifeMeaning; }; class LifePropagate1 :virtual public Life { public: LifePropagate1() :LifePropagate1Meaning(0x50) { } public: int LifePropagate1Meaning; }; class LifePropagate2 :virtual public Life { public: LifePropagate2() :m_B(0x60) { } public: int m_B; }; class NewLife :public LifePropagate1, public LifePropagate2 { public: NewLife() :NewLifeMeaning(0x100) { } public: int NewLifeMeaning; }; int main() { NewLife NewLifeObject; return 0; }
内存窗口观察NewLifeObject对象的基地址:
LifePropagate1与LifePropagate2 共用一个虚基类。最终虚基类的数据只有一份,数据冗余和二义性问题不再。
那么,虚继承又是怎么解决这些烦人的问题的呢?
可以看到在B和C中不再保存Life中的内容,保存了一份偏移地址,然后将最原始父类的数据保存在一个公共位置处这样保证了数据冗余性的降低同时,也消除了二义性。