【问题标题】:Understanding multiple inheritance in C++了解 C++ 中的多重继承
【发布时间】:2012-09-13 16:59:16
【问题描述】:

我正在尝试使用多重继承来解决我正在开发的复杂层次结构。情况如下:

class A {
  virtual void foo();
}

class B {
  virtual void foo();
}

class C : public B {

}

class D : public A, public C {
  void foo() { ... }
}

class ClientClass {
  void method() {
    A *a = new D();
    a->foo();
  }

我想知道的是:D 到底会不会只有一个功能foo()?我正在考虑它,因为该方法在父母双方都是虚拟的,所以他们应该喜欢准直到同一个,但我只是因为我来自 Java 并且我觉得它在 C++ 中可能有所不同。我必须声明虚函数foo() 两次,因为ClientClass 不知道BC,而只知道A。这是我想保留的要求。

编辑:即使AB 中的foo() 都是纯虚拟的,同样的答案是否适用? (例如= 0

【问题讨论】:

  • 我只是稍微摆弄了多重继承,所以我无法真正回答您的问题。不过,我要警告你,有大量程序员认为任何使用多重继承都会自动成为糟糕的设计。
  • @KRyan:我已经知道这一点,但这是我的选择,在这种特定情况下会派上用场。
  • 你能描述一下你为什么需要这个吗?

标签: c++ multiple-inheritance virtual-functions


【解决方案1】:

D 会有两个。可以在类中使用A :: foo()B :: foo() 访问它们。

【讨论】:

  • 但是他们会在所有情况下共享相同的实现吗?从某种意义上说,只在两个类的子类中实现foo() 是安全的,同时让只知道 A 或 C 的人可以使用该方法
  • 非常好的问题。它通常没有多大意义。这是一个令人头疼的问题,它导致使用其他答案中列出的虚拟继承重新设计基类。您应该仔细考虑导致您来到这里的设计。
【解决方案2】:

实际上,D::foo 的实现会同时覆盖A::fooB::foo

当然,这两个继承的函数仍然可用,可以通过它们的全名来调用。但这与单继承情况没有什么不同。

关于ClientClass,您创建一个D 对象,然后通过指向A 的指针调用foo。因此将调用D::foo 覆盖。

如果您想要使用不同版本的foo 来覆盖A::fooC::foo(例如,因为它们不相关但碰巧以相同的方式调用),那么您需要做一些工作:

class A2 : public A
{
public:
    virtual void foo()
    {
        A_foo();
    }
    virtual void A_foo()
    {
        A::foo();
    }
};
class C2 : public C
{
public:
    virtual void foo()
    {
        C_foo();
    }
    virtual void C_foo()
    {
        C::foo();
    }
};
class  D: public A2, C2
{
public:
    virtual void A_foo()
    { /* ... */ }
    virtual void C_foo()
    { /* ... */ }
};

现在使用:

{
    A *a = new D;
    a->foo(); //will call A2::foo -> D::A_foo
    B *b = new D;
    b->foo(); //will call C2::foo -> D::C_foo
    D *d = new D;
    d->foo(); //error: ambiguous call!
}

【讨论】:

  • 其实我犯了一个错误,ClientClass声明了一个A,但使用了实例化D(实际上它是从ClientClass外部设置的)。所以有效的实现将只是一个,两个名字会冲突到同一个名字?我必须声明 foo() 方法纯虚拟才能发生这种情况吗?
  • @Jack:好的,现在很清楚了。当然,它会调用D::foo。函数的纯粹性没有影响。
【解决方案3】:

D 类具有三个函数fooD::fooB::fooA::foo

由于fooAB 中都是虚拟的,因此将不合格的函数调用动态分派给最派生的函数:

void f(A & a) { a.foo(); }
void g(B & b) { b.foo(); }

int main()
{
    D d;
    f(d);       // calls d.D::foo();
    g(d);       // calls d.D::foo();

    d.foo();    // calls d.D::foo();
    d.B::foo(); // calls d.B::foo();
    d.A::foo(); // calls d.A::foo();
}

【讨论】:

    【解决方案4】:

    您遇到了多重继承的经典问题,可能是最重要的一个建议非常有限地使用它(通过混合或接口/虚拟类的概念)。

    查看this post from S.O 以获得更完整的解释和您拥有的各种替代方案。

    【讨论】:

    • 这里没有菱形继承,或者至少没有共同祖先。只有两个同名的方法,我想知道在这种情况下它也被认为是菱形继承。
    • 这不是臭名昭著的菱形关系,只是不同基类中的方法名称相同,因此虚拟继承没有任何用处!但是链接很好:D
    • 审查设计可能会发现需要虚拟继承,这会导致钻石关系,这有这些问题。
    【解决方案5】:

    在这段代码中:

    class ClientClass {
      void method() {
        A *a = new D();
        a->foo();
      }
    

    D 的 foo 将运行。你仍然可以使用 a->A::foo() 访问 A foo。 请注意,您初始化“a”的方式无法到达 B foo()。 但是每次构建 D 实例时,它都会使用 D foo,因为这两个 foo 函数都被声明为虚拟函数。

    对于代码中的每个类,您都有一个函数表。 每个对象的表由其类型定义。这意味着A * a = new D() 具有 A 功能表。 如果一个函数前面有一个虚拟标志,这意味着如果一个派生类的实例被构建,它的表将包含一个指向派生类函数的指针。

    这意味着在 ClinetClass 的代码中,“a”对象具有 A 函数表,其中 foo 函数包含指向 D foo 函数的指针。

    请注意,如果 D 没有它自己的 foo 并且你会写

    D * d =  new D();
    d-> foo();
    

    如果他不知道应该使用哪个 foo 函数,你会得到一个编译错误。

    【讨论】:

      猜你喜欢
      • 2014-05-16
      • 2011-08-06
      • 1970-01-01
      • 2014-06-23
      • 2012-06-10
      • 2011-03-29
      • 2017-03-31
      • 2012-05-14
      相关资源
      最近更新 更多