自学内容网 自学内容网

菱形继承(钻石继承)

C++ 中,钻石继承菱形继承是同一种继承结构的不同称呼。它们指的是在多重继承情况下出现的一种特定问题,即当一个类通过两个不同的路径继承自同一个基类时,形成的继承结构类似于菱形或钻石的形状。

钻石继承/菱形继承的结构:

假设有四个类,分别是 ABCD,继承关系如下:

      A
     / \
    B   C
     \ /
      D
  • BC 都继承自类 A
  • D 同时继承自类 BC

这样就形成了一个菱形结构,类 D 通过两条路径继承自 A,即 DBADCA

钻石继承的潜在问题:

在这种继承结构下,如果不加以控制,类 D 会从 A 继承两次,分别通过 BC,这可能导致以下问题:

  1. 冗余继承问题D 类中会存在两个 A 类的实例,分别通过 BC,这可能会引发数据冗余和不一致性。
  2. 模糊性问题:如果 A 类有一个成员变量或成员函数,而 D 继承了两个 A 实例,那么在 D 中调用这个成员时,编译器会无法判断你希望调用哪个 A 的成员,从而引发歧义。

解决钻石继承问题的方法:虚继承

为了避免上述问题,C++ 提供了 虚继承virtual inheritance)的机制。通过虚继承,类 BC 可以共享一个从 A 继承的实例,而不是各自独立拥有一个 A 实例。

虚继承的语法:

在定义类 BC 时,使用 virtual 关键字声明虚继承:

class A {
public:
    int value;
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};
  • 通过 BC 继承 A 时声明 virtual public A,表示 BC 虚继承自 A
  • D 继承 BC 时,由于虚继承的存在,A 的实例在 D 中只有一个,从而解决了冗余和歧义的问题。

思考

如果A中有一个虚函数x,这个虚函数在B和C都有重写,而在D中没有被重写, 请问,在使用D的实例的时候, 我用函数x, 实现应该是在哪个类里面

在 C++ 中,如果类 A 有一个虚函数 x,并且它在类 BC 中都进行了重写,而类 D 没有重写该虚函数,那么当你使用 D 的实例并调用 x 时,虚函数的具体实现取决于 D 对象的继承路径

因为 DBC 继承,所以调用的 x 函数的实现取决于具体的继承关系以及对象的构造路径。

钻石继承中调用虚函数的行为:

假设我们有以下继承结构:

class A {
public:
    virtual void x() { std::cout << "A::x" << std::endl; }
};

class B : virtual public A {
public:
    void x() override { std::cout << "B::x" << std::endl; }
};

class C : virtual public A {
public:
    void x() override { std::cout << "C::x" << std::endl; }
};

class D : public B, public C {
    // D doesn't override x()
};
当调用 D 的虚函数 x() 时:
  • 由于 D 没有重写 x,所以它会沿着继承链查找实现。
  • 在 C++ 中,虚函数的解析是基于对象的动态类型,而不是声明的静态类型。因此,具体调用哪个版本的 x 取决于从哪个继承路径访问 x 函数。
  • D 继承自 BC,但由于 BC 都虚继承自 A,最终只有一个 A 的实例。
调用结果:
  • 如果你通过 D 的实例直接调用 x() 函数,那么 C++ 会根据虚函数的机制选择最派生的版本。
  • D 中,没有重写 x(),所以编译器会在 D 的基类中查找虚函数 x() 的实现。因为 BC 都重写了 x(),并且类 D 在继承链上最终从 C 继承了 x(),所以会选择 C::x() 作为最终的实现。
代码示例:
int main() {
    D d;
    d.x(); // 输出: "C::x"
    return 0;
}

解释:

  • D 没有重写 x(),因此调用时会寻找继承链上的实现。
  • 虚函数的调用基于最派生的实现,即类 C 中的 x()
  • 虽然 B 也重写了 x(),但由于 D 是按 public B, public C 的顺序继承,最终会优先调用 C::x()

如果顺序不同:

如果继承顺序是 class D : public C, public B,则 B::x() 会被调用。继承顺序在 C++ 中会影响虚函数的最终选择。

示例:
class D : public C, public B { };

int main() {
    D d;
    d.x(); // 输出: "B::x"
    return 0;
}

在多重继承中,如果没有重写虚函数,C++ 会沿着继承链查找虚函数的实现。虚继承的结构下,如果基类 BC 都重写了虚函数,那么在 D 中调用该函数时,哪个基类的实现会被调用,取决于继承链和类的定义顺序。

总结:

  • 钻石继承菱形继承是同一件事,都是指 C++ 多重继承中的一种特殊继承结构。
  • 在这种结构下,如果不使用虚继承,可能会导致多个基类实例和歧义问题。
  • 通过虚继承可以解决这些问题,确保只存在一个基类实例。

原文地址:https://blog.csdn.net/qq_31638535/article/details/142983683

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!