【Example】C++ 虚基类与虚继承 (菱形继承问题)
C++ 是支持多继承的语言,但是实际项目开发中非必要不要使用多继承以降低代码逻辑的复杂性,当然 C++ 多继承的特性带来一些问题即菱形继承。
当一个类继承了两个来自同父类的子类后,会产生命名空间冲突及资源冗余。
【伪代码】
class Base{ public: int gem = 0; }; class Byte : public Base{}; class Expert : public Base{}; class Blu : public Byte, public Expert{ public: // Error 字段不明确 void SetGem(int i) { this->gem = i; } }
可以看到,产生错误的主要原因是,Byte 和 Expert 同样都继承了 Base,然后 Blu 又继承了 Byte 和 Expert 。
根据 C++ 类继承的机制,子类的大小=父类大小+子类自身成员大小。
因此,可以看出,实际上 Blu 类当中存在两个 Gem 成员变量,分别来自 Byte 和 Expert,使用 this 指针进行调用,会发生命名空间冲突错误,同时造成了资源的重复浪费。
解决的方法也很简单,使用虚继承的方式:
【伪代码】
class Base{}; class Byte : virtual public Base{}; class Expert : virtual public Base{};
可以看到,在 Byte 和 Expert 的继承后面使用了 virtual 关键字进行了修饰。
这时,Base 便成了 Byte 和 Expert 的虚基类,达成了虚继承的方式,Base 类在最终的 Blu 类中只存在一个,所以不存在命名空间冲突及资源浪费。
虚基类并不是“绝对的”,而是“相对的”:虚基类在它自身声明、定义的时候无需任何修饰,只是在子类继承时进行 virtual 修饰。
然而这又牵扯到了另一种错误:
【伪代码】
class Base{}; class Byte : virtual public Base{}; class Expert : virtual public Base{}; class Frog : public Base{}; class Blu : public Byte, public Expert, public Frog{};
可以看到,Blu 继承了 Byte 、Expert、Frog 三个类,但是 Frog 类不是以虚继承的方式继承 Base 的。
所以在 Blu 类中仍然存在菱形继承的问题,所有需要将所有继承同一基类的上级父类继承方式声明为 virtual。
同时,在虚继承机制当中,虚基类是由最终的派生类进行初始化的,本身达成了一种 “间接继承” 的关系。
也就意味着最终的派生类在构造函数初始化中,要在初始化表中调用虚基类的构造函数进行初始化。
这样,就保证了虚基类不会被二次初始化。
最终正确代码是这样的:
class Base { public: Base() {}; ~Base() {}; public: int gem = 1; }; class Byte : virtual public Base { public: Byte() {}; ~Byte() {}; }; class Expert : virtual public Base { public: Expert() {}; ~Expert() {}; }; class Blu : public Byte, public Expert { public: // 调用虚基类构造函数 Blu() : Base(),Byte(),Expert() {}; ~Blu() {}; void SetGem(int i) { this->gem = i; } };