如何在多重继承中实现多态行为?

5

我从未使用过多重继承,但最近阅读相关资料时开始思考如何在我的代码中实际应用它。通常情况下,当我使用多态性时,我通常会创建新的派生实例,并声明为基类指针,例如:

 BaseClass* pObject = new DerivedClass();

当我在调用派生类的虚函数时,希望能够获得正确的多态行为。这样一来,我就可以拥有不同多态类型的集合,并通过它们的虚函数来管理自己的行为。

考虑使用多重继承时,我在思考同样的方法,但如果我有以下继承层次结构,该怎么办呢?

 class A {
     virtual void foo() = 0;
 };
 class B : public A {
     virtual void foo() {
         // implementation
     }
 };

 class C {
     virtual void foo2() = 0;
 };
 class D : public C {
     virtual void foo2() {
         // implementation
     }
 };

 class E : public C, public B {
     virtual void foo() {
         // implementation
     }
     virtual void foo2() {
         // implementation
     }
 };

使用这个层次结构,我可以创建一个类E的新实例,如下所示:
 A* myObject = new E();

或者

 C* myObject = new E();

或者
 E* myObject = new E();

但是,如果我将其声明为 A*,那么我将失去类C和D继承层次结构的多态性。同样地,如果我将其声明为 C*,那么我就会失去类A和B的多态性。如果我将其声明为 E*,那么我通常无法通过基类指针访问对象,因此无法获得多态行为。

那么我的问题是,有什么解决方法吗?C++是否提供了一种可以解决这些问题的机制,还是必须在基类之间反复转换指针类型?显然,这非常麻烦,因为我不能直接执行以下操作:

 A* myA = new E();
 C* myC = dynamic_cast<C*>(myA);

由于该强制转换将返回一个NULL指针,因此需要注意。

3
“如何实际应用它”听起来像你只是因为可能性而想尝试使用它。请仅在有充分理由时使用它。请不要只是因为可以使用而使用它。 - Alex
@Vash 我完全同意 - 我曾考虑过在实践中使用它,但由于我上面提到的问题而放弃了。 - mathematician1975
@mathematician1975 如果你想要访问C类的多态性,那么你应该使用C* myC = dynamic_cast<C*>(myA);这样做吗? - Mr.Anubis
@Mr.Anubis 是的,我的错误,它应该是C*。 - mathematician1975
然后,如果您更正了拼写错误,程序应该可以正常运行 :) - Mr.Anubis
显示剩余2条评论
4个回答

8

使用多重继承,你可以看到一个对象有多个不同的视图。比如:

class door { 
   virtual void open();
   virtual void close();
};

class wood { 
    virtual void burn();
    virtual void warp();
};

class wooden_door : public wood, public door {
    void open() { /* ... */ }
    void close() { /* ... */ }
    void burn() { /* ... */ }
    void warp() { /* ... */ }
};

现在,如果我们创建了一个木门对象,我们可以将其传递给期望使用(引用或指针)门对象的函数,或者期望使用(同样是指针或引用)wood对象的函数。
当然,多重继承不会突然赋予处理door的函数任何处理wood的新功能(反之亦然)--但我们并不真正期望这种情况。我们期望能够将我们的木门视为可以打开和关闭的门,或者作为可以燃烧或变形的木材 - 这正是我们得到的结果。

3
在这种情况下,类AC是接口,而E实现了两个接口。(通常,在这种情况下,您不会有中间类CD。)有几种处理方法。
最常见的方法可能是定义一个新接口,该接口是AC的总和:
class AandC : public A, public C {};

并且从中派生E。然后通常通过AandC*来管理E,可以将其不加区分地传递给需要A*C*的函数。需要在同一对象中使用两个接口的函数将处理AandC*

如果接口AC有某种关联,比如C提供了一些额外的功能,某些A(但不是全部)可能希望支持这些功能,则A具有getB()函数可能是有意义的,该函数返回C*(如果对象不支持C接口,则返回空指针)。

最后,如果您拥有混合类和多个接口,则最清晰的解决方案是维护两个独立的层次结构,一个用于接口,另一个用于实现部分:

//   Interface...
class AandC : public virtual A, public virtual C {};

class B : public virtual A
{
    //  implement A...
};

class D : public virtual C
{
    //  implement C...
};

class E : public AandC, private B, private D
{
    //  may not need any additional implementation!
};

我很想说从设计角度来看,接口的继承应该总是虚拟的,以便在未来允许这种情况即使现在不需要。但实际上,事实证明,无法预测此类使用情况似乎相当罕见。
如果您想了解更多关于这方面的信息,您可能需要阅读Barton和Nackman的书。虽然该书现在已经过时(描述C++98之前的内容),但大部分信息仍然有效。

1

这应该可以工作

 A* myA = new E();
 E* myC = dynamic_cast<E*>(myA);
 myC->Foo2();

0

C 无法转换为 A,因为它不是一个 A;它只能向下转换为 DE

使用 A*,您可以创建一个 E*,通过它,您总是可以明确地说出像 C::foo() 这样的东西,但是,是的,A 没有办法隐式调用在 C 中可能具有覆盖或可能没有的函数。

在这种奇怪的情况下,模板通常是一个很好的解决方案,因为它们可以让类表现得像它们具有共同的继承关系,即使它们实际上并没有。例如,您可以编写一个模板,它可以与任何可以调用 foo2() 的东西一起使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接