菱形继承
菱形继承:指有一个基类被两个不同的类所继承,且存在一个类继承于这两个类而形成的一种菱形关系,故称菱形继承。如下图所示:
菱形继承存在的问题
- 二义性(ambiguity):当派生类同时继承自两个或更多个基类,而这些基类又共同继承自一个公共基类时,会出现二义性。如果基类中有相同的成员函数或成员变量,编译器无法确定应该调用哪个基类中的成员,从而导致编译错误。
- 冗余数据(redundant
data):由于派生类继承了多个相同的基类,这些基类可能包含相同的成员变量。这样就会导致派生类中存在多份相同的数据,造成内存空间的浪费。
假设Person类有一个成员变量 age,Student类的对象中包含了这个 age 成员,Teacher类的对象中也包含了 age 成员,但是 Assistant 类依次继承了 Student 和 Teacher类,不考虑其他成员,就最终结果而言,Assistant类中确实包含了两份 age 成员。
这就导致了菱形继承的 冗余性 和 二义性。
- 冗余性:存在重复的数据,比如 age要存两份
- 二义性:如果要访问 age 成员,是访问 Student 类的 age,还是访问 Teacher类的 age.
菱形继承的解决方案 —— 作用域解析运算符(::)
如果虚继承不适用或不可行,可以使用作用域解析运算符来指定调用哪个基类的成员。通过指定基类名称和作用域解析运算符,可以消除二义性。例如:
class A {
public:void funcA() { cout << "A::funcA()" << endl; }
};
class B : public A {
public:void funcB() { cout << "B::funcB()" << endl; }
};
class C : public A {
public:void funcC() { cout << "C::funcC()" << endl; }
};
class D : public B, public C {
public:void funcD() { cout << "D::funcD()" << endl; }
};int main() {D obj;obj.funcA(); // 调用 A::funcA()obj.funcB(); // 调用 B::funcB()obj.funcC(); // 调用 C::funcC()obj.B::funcA(); // 调用 B::funcA()obj.C::funcA(); // 调用 C::funcA()return 0;
}
菱形继承的解决方案 —— 虚拟继承
为了解决菱形继承所带来的问题,C++引入了虚继承(virtual inheritance)。虚继承通过在公共基类前加上virtual关键字来声明,在派生类中只保留一份公共基类的实例,从而避免了冗余数据和二义性的问题。
其实就是将 Student 类和 Teacher 类重复的部分,放到一个公共位置,访问的时候,直接访问这块公共位置。
虚继承的主要作用:
是确保在多重继承中,共享的基类只有一份实例。这样可以避免冗余数据和二义性的问题。
// 下面是一个示例代码,展示了如何使用虚继承解决菱形继承的问题:
class Animal {
public:int age;
};
class Mammal : virtual public Animal {
};
class Bird : virtual public Animal {
};
class Platypus : public Mammal, public Bird {
};
int main() {Platypus p;p.age = 5; // 直接访问公共基类的成员return 0;
}
/*
通过使用虚继承,派生类Platypus中只保留了一份公共基类Animal的实例,避免了冗余数据。
此外,我们可以直接访问公共基类Animal的成员变量,而不会出现二义性。
*/
需要注意的是,虚继承可能会引入一些额外的开销,因为需要在内存中存储虚基类的指针或偏移量。此外,虚继承可能会导致一些设计上的复杂性,因此在使用虚继承时需要权衡利弊,并根据具体情况选择合适的解决方案。
虚继承的主要作用:
是确保在多重继承中,共享的基类只有一份实例。这样可以避免冗余数据和二义性的问题。
虚继承(virtual inheritance)不仅可以用于解决菱形继承中的二义性问题,还可以在其他情况下使用。在以下情况下,虚继承也是一种有用的工具:
- 多重继承中的共享基类:当多个派生类需要共享同一个基类时,可以使用虚继承。通过使用虚继承,这些派生类只会包含一份共享基类的数据和函数。
- 基类作为接口:如果一个基类被用作接口,而派生类需要实现这个接口,但同时又需要从其他基类继承其他功能,可以使用虚继承。这样可以确保派生类只实现接口,并且不会出现二义性问题。
- 多层次的继承关系:在多层次的继承关系中,如果存在需要共享的基类,可以使用虚继承来减少重复数据和解决二义性问题。
总之,虚继承不仅仅用于解决菱形继承中的问题,也可以在其他情况下使用,以确保共享基类只有一份实例。