多重继承+虚函数混乱

时间:2009-03-05 19:59:14

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

我有像这样的钻石多重继承场景:

    A
  /   \
 B     C
  \   /
    D

公共父A定义了一个虚函数fn() B和C是否可以定义fn()
如果是,那么下一个问题是 - D可以访问B和C的fn()而不消除歧义吗?我假设有一些语法...
如果不明确知道B和C是谁,D有可能做到这一点吗? B和C可以替换为其他类,我希望D中的代码是通用的。

我要做的是让D以某种方式枚举它在其祖先中拥有的fn()的所有实例。这在虚拟功能的其他方面是否可行?

6 个答案:

答案 0 :(得分:18)

除非您在fn中再次覆盖D,否则不可能。因为D对象中没有最终覆盖:CB都覆盖A::fn。您有几种选择:

  • 删除C::fnB::fn。然后,仍然覆盖A::fn的那个具有最终的覆盖。
  • 在D中放置最后一个覆盖。然后,那个覆盖A::fn以及fnC中的B

例如,以下结果导致编译时错误:

#include <iostream>

class A {
public:
    virtual void fn() { }
};

class B : public virtual A {
public:
    virtual void fn() { }
};

class C : public virtual A {
public:
    virtual void fn() { }
};

// does not override fn!!
class D : public B, public C {
public:
    virtual void doit() {
        B::fn();
        C::fn();
    }
};

int main(int argc, char **argv) {
  D d;
  d.doit();
  return 0;
}

但是,你可以从C和B中的A派生非虚拟,但是你再也没有钻石继承了。也就是说,A中的每个数据成员在B和C中出现两次,因为在D对象中有两个A基类子对象。我建议你重新考虑一下这个设计。尝试消除需要虚拟继承的双重对象。它经常导致这种冲突的情况。

与此非常相似的情况是您要覆盖特定功能。想象一下,你有一个在B和C中具有相同名称的虚函数(现在没有公共基数A)。在D中,您希望覆盖每个函数,但为每个函数赋予不同的行为。根据您是使用B指针还是C指针调用该函数,您具有不同的行为。 Herb Sutter的Multiple Inheritance Part III描述了一种很好的方法。它可能会帮助您决定您的设计。

答案 1 :(得分:4)

第一个问题,是的,B和C可以将fn()定义为虚函数。 其次,D当然可以通过使用范围运算符来访问B::fn()C::fn() :: 第三个问题:D必须至少知道B和C,因为你必须在继承列表中定义它们。您可以使用模板打开B和C的类型:

class A
{
public:
   virtual ~A() {}
   virtual void fn() = 0;
};

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

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

template <typename TypeB, typename TypeC>
class D: public TypeB, public TypeC
{
public:
   void Do()
   {
      static_cast<TypeB*>(this)->fn();
      static_cast<TypeC*>(this)->fn();
   }
};

typedef D<B, C> DInst;

DInst d;
d.Do();

关于自动枚举D继承自的所有类的所有fn()函数的愿望:我不确定是否可以在不诉诸MPL的情况下实现。至少你可以使用处理3个或更多模板参数的版本扩展我的上面的例子,但我想有一个上层(内部编译器)限制类模板参数的数量。

答案 2 :(得分:2)

如果您确实需要能够跟踪祖先并通过类型进行枚举,则可能需要查看Loki TypeLists。如果没有一堆工作,我不确定你所要求的是否真的可行。确保你没有在这里过度工程。

稍微不同的是,如果你打算以这种方式使用MI(例如,可怕的钻石),那么你应该非常清楚你想要哪个虚拟成员。在编写B::fn()时没有明确做出决定时,我想不出一个好的情况,你想要选择C::fn()超过D的语义。您可能会根据单个方法的作用选择一个(或两个)。一旦做出决定,要求是继承的更改不会改变期望或语义界面。

如果您真的担心更换新课程,请说E代替B,其中E不会从B下降但提供相同的界面,那么你应该真的使用模板方法,虽然我不确定为什么那里有static_cast<> ......

struct A {
    virtual ~A() {}
    virtual void f() = 0;
};
struct B: A {
    virtual void f() { std::cout << "B::f()" << std::endl; }
};
struct C: A {
    virtual void f() { std::cout << "C::f()" << std::endl; }
};

template <typename Base1, typename Base2>
struct D: Base1, Base2 {
    void g() { Base1::f(); Base2::f(); }
};

int main() {
    D<B,C> d1;
    D<C,B> d2;
    d1.g();
    d2.g();
    return 0;
}

// Outputs:
//   B::f()
//   C::f()
//   C::f()
//   B::f()

工作正常,看起来更容易一些。

答案 3 :(得分:1)

已经有几个问题可以解决这个问题。好像我们已经没有问题了。也许搜索框应该大于Ask Question按钮。

答案 4 :(得分:1)

您无法枚举祖先中fn()的定义。 C ++缺乏反思。我能想象的唯一方法是测试所有可能祖先的typeid的巨型循环。想象一下很痛苦。

答案 5 :(得分:0)

Vividos已经回答了帖子的主要部分。即使我使用范围运算符而不是更麻烦的static_cast&lt;&gt; +解除引用运算符。

根据手头的任务,也许您可​​以将继承关系从D更改为B和C,以获得更少的耦合组合(加上可能从A继承)。这假设你不需要将D作为B或C进行polimorphically使用,并且你并不需要B和C共享相同的基本实例。

如果你选择合成,你可以接收B和C作为构造函数的参数作为类型A的引用/指针,使D完全不知道类型B和C.此时,你可以使用容器来保持尽可能多的派生对象。您自己实现的fn()(如果您决定)或任何其他方法。