面试题答案
一键面试代码实现
#include <iostream>
// 基类
class Base {
public:
void print() {
std::cout << "Base::print()" << std::endl;
}
};
// 第一层派生类
class Derived1 : virtual public Base {
};
class Derived2 : virtual public Base {
};
// 第二层派生类
class Intermediate1 : public Derived1 {
};
class Intermediate2 : public Derived2 {
};
// 第三层派生类
class Final : public Intermediate1, public Intermediate2 {
};
解决二义性的方法及优缺点
使用virtual
继承
- 优点:
- 能够从根本上解决菱形继承带来的二义性问题。在这种方式下,无论继承体系多么复杂,最终派生类只会有一份基类的成员,避免了数据冗余。
- 对于需要共享基类状态的场景,
virtual
继承确保所有派生类都访问同一个基类实例,保证了数据一致性。
- 缺点:
- 增加了额外的空间和时间开销。
virtual
继承需要额外的指针(虚基表指针)来定位虚基类的位置,这会增加对象的大小。在访问虚基类成员时,由于需要通过指针间接访问,也会带来一定的时间开销。 - 实现相对复杂,尤其是在多层继承和复杂的继承关系中,理解和维护代码的难度会增加。
- 增加了额外的空间和时间开销。
作用域限定符明确指定
在Final
类中如果要调用Base
类的print
方法,可以使用作用域限定符明确指定:
void Final::callBasePrint() {
Intermediate1::Base::print();
}
- 优点:
- 简单直接,不需要对继承体系进行大的修改。在代码量较小且继承关系相对简单时,使用作用域限定符可以快速解决二义性问题。
- 对于临时解决二义性问题很方便,不需要引入
virtual
继承带来的额外开销。
- 缺点:
- 没有从根本上解决二义性,只是在调用时明确指定路径。如果在不同地方都需要调用,代码中会有很多重复的作用域限定符,代码冗余且不优雅。
- 不能解决数据冗余问题,如果存在数据成员,会存在多份拷贝,可能导致数据不一致。
重新定义函数
在Final
类中重新定义从基类继承来的函数:
class Final : public Intermediate1, public Intermediate2 {
public:
void print() {
Intermediate1::Base::print();
}
};
- 优点:
- 对外接口统一,使用者不需要关心内部复杂的继承关系,只需要调用
Final
类的print
方法。 - 可以在重新定义的函数中进行一些额外的逻辑处理,例如在调用基类方法前后添加自定义逻辑。
- 对外接口统一,使用者不需要关心内部复杂的继承关系,只需要调用
- 缺点:
- 没有解决数据冗余问题,同样会存在多份基类数据成员拷贝。
- 如果基类的函数实现发生变化,需要在所有重新定义的地方进行修改,维护成本较高。而且如果不小心遗漏修改,可能导致逻辑错误。