通过多重继承合并抽象和最终方法

Merging abstract and final methods through multiple inheritance

考虑以下程序:

#include <stdio.h>
#include <memory>

using namespace std;

class WheeledVehicle {
  public:
    virtual void drive() const { printf("Default driving implementation."); }
};

class GroundVehicle : public WheeledVehicle {
  public:
    void drive() const final { WheeledVehicle::drive(); }
};

class DrivableAssaultMachine {
  public:
    virtual void drive() const = 0;
    void fire_cannon();
};

class FighterJet : public DrivableAssaultMachine {
  public:
    void fly() const { printf("flying my plane.\n"); }
    void drive() const override { fly(); }
};

class Tank : public DrivableAssaultMachine, public GroundVehicle {
};

int main(int argc, char **argv) {
  make_shared<Tank>()->drive();
  return EXIT_SUCCESS;
}

我想使用对 Tank 修改来编译该程序。如果我实现 drive,那么它与 GroundVehicle 中的 final 声明相矛盾。如果我不去实现,那么Tank就是抽象的,无法实例化。在从 DrivableAssaultMachine 继承的上下文中,是否有其他方法可以识别 Tankdrive() 的继承实现?

编辑: 重命名所有内容以提供更具体的示例。

概念缺陷:

class FighterJet 无法从 DrivableAssaultMachine 派生为 DrivableAssaultMachine 派生自 class GroundVehicle.

FighterJet 不是 GroundVehicle

您应该区分 GroundVehiclesAirVehicles 并添加: drivefly 作为成员函数,然后 然后 从第一个导出 Tank,从第二个导出 FighterJet

第 n 次编辑后:

您刚刚考虑了我告诉您的内容并更改了继承结构,从 WheeledVehicle

制动 DrivableAssaultMachine

最终答案:

If I implement drive(), then it contradicts the final declaration in GroundVehicle.

drive()FighterJet 中是多余的(因为它有 fly() 可用),从那里删除它可以让你在 Tank.[=45 中覆盖它=]

DrivableAssaultMachine中的virtual drive与继承自WheeledVehicledrive()无关。因此,当尝试在 Tank 中覆盖它时应该存在歧义,因为它具有作为基础 class 的 DrivableAssaultMachine 和间接 WheeledVehicle.

注意:一般来说,你的抽象 class 应该提供基本的功能,例如成员函数:一个虚拟的 move() 然后派生的 classes 应该把它包装起来(覆盖它) 并将其专门化以满足他们的需求,例如:drive()fly()。指定可见性和可访问性的唯一工具是在定义和继承期间通过 publicprivateprotected 说明符。

C++ 不是 Java.

C++ 中的继承和覆盖必须比 Java 中的更明确。 (我认为 C++ 就在这里。)

在 C++ 中,两个虚函数声明 foo(具有相同的签名)被认为是 同音异义、不同的、不相关的函数,除非一个声明覆盖了另一个。

struct B1 {
    virtual void foo();
};

struct B2 {
    virtual void foo();
};

struct D : B1, B2 {
    void bar();
};

D 有两个具有相同签名的成员函数的继承声明,在 D::bar() 中对 foo() 的不合格调用会产生歧义,但是 [=81= 的定义] D 格式正确。 在 C++ 中存在潜在的歧义没有问题,只有歧义调用是不允许的。

这意味着您可以从基础 classes 派生出不相关的同音异义词。您只需要避免模棱两可的调用即可。

即使 foo() 的两个声明来自同一个 class:

struct B {
    virtual void foo();
};

struct Left : B {
};

struct Right : B {
};

struct D : Left, Right {
    void bar();
};

B::foo() 的两个继承声明只是同音异义词:它们来自同一个 class,但它们在两个不同的基础 class 中。 C++ 让你拥有同音异义词。 编译器不相信这些函数做同样的事情或者除了具有相同的名称、显式参数列表(括号内的参数)、隐式 this 参数类型(类型 Base*),并在源文件中由相同的声明声明!

当然,存在潜在的歧义:如果您尝试在派生对象上调用 foo,或尝试转换(隐式地,或显式地使用 static_cast 或 C 风格的转换)指向不明确基数 class 指针的派生指针。

[注意:有些人称其为 "diamond inheritance"(尽管继承图是树而不是 "diamond"),或 "dreaded diamond"(如果您不理解,这只会令人恐惧C++ 中的 MI)或 "diamond of death"(谁死了?)或 "diamond of end of the world"(现在我正在编造一些东西,但它只比 "diamond of death" 稍微夸张一点)。说真的,不要吓到有潜在歧义的人。 --尾注]

这意味着您可以从基 classes 派生出同一个非虚拟基 class 的不相关派生。您只需要避免模棱两可的调用和模棱两可的转换。 (当两个 class 像 refcounted_base 一样从同一个实用程序 class 派生时,就会发生这种情况。)

在Java中,classes的多重继承不存在,但多重接口继承存在,并且给定(纯虚拟)函数的不同声明是同义词:

interface I {
    void foo();
}

class Base {
    public void foo() { }
}

class Der extends Base implements I {
}

此处 Base::foo() 将实施 I::foo(),即使 BaseI 都没有命名或彼此不认识。这在 C++ 中永远不会发生。

Although my feeling is that the compiler should be able to resolve ambiguities and virtualization here,

您的感觉是基于 C++ 继承模型的错误概念;编译器无法知道您的意图。您给了编译器两个不同的声明,仅仅因为它们是同音异义词并不意味着您打算使用同义词

通过从公共接口继承(使用接口应有的虚拟继承),您是在告诉编译器只有一个特性 drivedrive 的所有用法现在都是同义词。

我通过创建菱形层次结构并使用虚拟继承解决了这个问题。以下是问题层次结构的简化可视化:

                    WheeledVehicle
                                \
  DrivableAssaultMachine      GroundVehicle
                       \     /
                        Tank

虽然我的感觉是编译器应该可以解决这里的歧义和虚拟化,但事实是它不能。我的方案是全连接钻石,使用虚拟继承:

                       Drivable
                      /       \
                     /       WheeledVehicle
                    /          /
  DrivableAssaultMachine      GroundVehicle
                       \     /
                        Tank

这是最终的程序:

#include <stdio.h>
#include <memory>

using namespace std;

class Drivable {
  public:
    virtual void drive() const = 0;
};

class WheeledVehicle : public virtual Drivable {
  public:
    virtual void drive() const override { printf("Default driving implementation.\n"); }
};

class GroundVehicle : public WheeledVehicle {
  public:
    void drive() const final { WheeledVehicle::drive(); }
};

class DrivableAssaultMachine : public virtual Drivable {
  public:
    void fire_cannon();
};

class Tank : public DrivableAssaultMachine, public GroundVehicle {
};

int main(int argc, char **argv) {
  make_shared<Tank>()->drive();
  return EXIT_SUCCESS;
}

一般来说,解决方法似乎是在继承树的根部附近使用简单的虚拟接口,并在接近叶子时组合或复杂化接口。