菱形继承带来的弊端
- 数据冗余:在菱形继承结构中,从最派生类访问菱形顶端基类的成员时,会存在数据冗余。例如,若顶端基类有成员变量
data
,在菱形底部的派生类中会存在多份data
的拷贝,浪费内存空间。
- 访问歧义:由于存在多份基类成员的拷贝,当在最派生类中访问菱形顶端基类成员时,编译器无法明确知道该访问哪一份拷贝,从而产生访问歧义。
解决方案及优缺点
虚继承
- 实现方式:在继承关系中,通过在基类前加上
virtual
关键字来声明虚继承。例如:
class Base {
public:
int data;
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Final : public Derived1, public Derived2 {};
- 优点:
- 解决了数据冗余问题,在最派生类中,菱形顶端基类成员只保留一份拷贝。
- 消除了访问歧义,因为只有一份基类成员,编译器不会出现不知道访问哪一份拷贝的情况。
- 缺点:
- 实现复杂,编译器需要额外维护虚基类表等结构,增加了内存开销和运行时开销。
- 虚继承可能会使代码可读性变差,尤其是在复杂的继承体系中。
接口继承
- 实现方式:将菱形顶端基类定义为纯接口类(包含纯虚函数的抽象类),具体实现由中间派生类各自完成。例如:
class Interface {
public:
virtual void func() = 0;
};
class Derived1 : public Interface {
public:
void func() override { /* 具体实现 */ }
};
class Derived2 : public Interface {
public:
void func() override { /* 具体实现 */ }
};
class Final : public Derived1, public Derived2 {};
- 优点:
- 避免了数据冗余问题,因为纯接口类不包含数据成员。
- 减少了访问歧义,因为具体实现由中间派生类负责,各自明确。
- 缺点:
- 对于需要共享数据和实现的场景不适用,因为纯接口类不能有数据成员和实现代码。
- 增加了代码的复杂度,需要在中间派生类中重复实现一些逻辑。
组合方式
- 实现方式:在派生类中,通过组合的方式包含菱形顶端基类对象,而不是通过继承。例如:
class Base {
public:
int data;
};
class Derived1 {
private:
Base base;
public:
// 提供访问base成员的接口
int getData() { return base.data; }
};
class Derived2 {
private:
Base base;
public:
// 提供访问base成员的接口
int getData() { return base.data; }
};
class Final {
private:
Derived1 d1;
Derived2 d2;
public:
// 提供访问d1和d2中base成员的接口
int getDataFromD1() { return d1.getData(); }
int getDataFromD2() { return d2.getData(); }
};
- 优点:
- 清晰地控制了数据的访问和管理,每个派生类对包含的基类对象有明确的控制权。
- 避免了继承体系中的一些复杂问题,如数据冗余和访问歧义。
- 缺点:
- 增加了代码量,需要为包含的对象提供额外的访问接口。
- 破坏了继承的层次结构,对于一些依赖继承关系的设计模式不太友好。