MST

星途 面试题库

面试题:C++多重继承二义性消除在复杂继承体系中的运用

设计一个复杂的C++多重继承体系,其中至少包含四层继承关系,且在中间层存在多个类继承自相同基类导致二义性问题。要求写出代码解决这些二义性,并且说明在这种复杂结构下不同消除方法的优缺点。
24.4万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

代码实现

#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方法。
    • 可以在重新定义的函数中进行一些额外的逻辑处理,例如在调用基类方法前后添加自定义逻辑。
  • 缺点
    • 没有解决数据冗余问题,同样会存在多份基类数据成员拷贝。
    • 如果基类的函数实现发生变化,需要在所有重新定义的地方进行修改,维护成本较高。而且如果不小心遗漏修改,可能导致逻辑错误。