了解C ++中的多重继承

时间:2012-09-13 16:59:16

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

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

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。这是我想要保留的要求。

编辑:即使foo()A中的B都是纯虚拟,也会应用相同的答案吗? (例如= 0

5 个答案:

答案 0 :(得分:3)

D将有两个。可以使用课程内的A :: foo()B :: foo()访问它们。

答案 1 :(得分:2)

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

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

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

如果您想要覆盖fooA::foo的不同版本的C::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!
}

答案 2 :(得分:2)

课程D有三个功能fooD::fooB::fooA::foo

由于fooA中的B都是虚拟的,因此会将非限定函数调用动态调度到最派生的函数:

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();
}

答案 3 :(得分:1)

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

请查看this post from S.O以获得更全面的解释和各种替代方案。

答案 4 :(得分:0)

在此代码中:

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

D's 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函数,你会得到一个编译错误。