【问题标题】:Multiple inheritance + virtual function mess多重继承+虚函数混乱
【发布时间】:2010-10-11 14:39:06
【问题描述】:

我有一个像这样的菱形多重继承场景:

    A
  /   \
 B     C
  \   /
    D

公共父级 A 定义了一个虚函数 fn()。
B和C都可以定义fn()吗?
如果是,那么下一个问题是——D 可以无歧义地访问 B 和 C 的 fn() 吗?我假设这有一些语法..
D 是否有可能在不明确知道 B 和 C 是谁的情况下这样做? B 和 C 可以被其他一些类替换,我希望 D 中的代码是通用的。

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

【问题讨论】:

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


    【解决方案1】:

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

    • 删除C::fnB::fn。然后,仍然覆盖A::fn 的那个拥有最终的覆盖者。
    • 在 D 中放置一个最终覆盖器。然后,该覆盖器覆盖 A::fn 以及 CB 中的 fn

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

    #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 描述了一个很好的方法。它可能会帮助您决定您的设计。

    【讨论】:

    • 我认为这不是 shoosh 想要做的。他说“我想做的是让 D 以某种方式枚举它在其祖先中拥有的所有 fn() 实例。”。他不想在 D 类中覆盖 fn()。
    • 这就是我说不可能的原因。如果 C 和 B 都覆盖了 A::fn,那么他不能在不覆盖 D 中的 fn 的情况下定义 D
    • 你说的“Drop”是什么意思?
    【解决方案2】:

    第一个问题,是的,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 个或更多模板参数的版本扩展我上面的示例,但我猜类模板参数的数量存在上限(内部编译器)。

    【讨论】:

    • 这可能是最接近我需要的解决方案。不幸的是,在我的情况下,继承(B,C)中的类数量也是可变的。猜测这将不得不等待 C++0x 的可变模板参数。
    • 想象一下你有这个代码:A *a = some_d_object; a->fn();将调用哪个版本的 fn?
    • @vividos:很有趣。根据 C++ 标准的第 10.3.3 节,类中的每个虚函数都必须有一个唯一的最终覆盖器才能“格式正确”——这意味着如果不是这种情况,编译器必须发出错误消息。但是我尝试过的每个编译器(Linux 上的 MSVC++9、MinGW、g++ 4.1.2)...
    • ... 确实允许您的代码编译和运行没有问题。 (我什至尝试过 Comeau C++,它是最严格的 C++ 之一,它也能正常工作!)只有在尝试实际调用模棱两可的函数 fn() 时才会产生编译器错误。所以从技术上讲,所有 4 个编译器都有错误......
    • @j_random_hacker "我错了什么" 你假设D 中有一个 基类A(试试A&amp; = d;),所以有一个成员函数A::fn()(试试d.A::fn();)。有两个基础A(检测 c|dtors 以查看它们的地址),所以d 中有两个成员A::fn()d.B::A::fn()d.C::A::fn()。他们每个人都确实有一个最终的替代者。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      如果您确实需要能够跟踪祖先和枚举类型,您可能需要查看Loki TypeLists。我不确定如果没有大量工作,您所要求的是否真的可能。确保您没有在这里过度设计。

      稍微不同的是,如果您打算以这种方式使用 MI(即,可怕的钻石),那么您应该非常明确地说明您想要哪个虚拟成员。我想不出一个好的案例,你想选择B::fn() 的语义而不是C::fn() 而不在编写D 时明确做出决定。您可能会根据单个方法的作用来选择一个(甚至两者)。做出决定后,要求继承的更改不会改变期望或语义接口。

      如果你真的担心换一个新类,用E 代替B,其中E 不是来自B 但提供相同的接口,那么你真的应该使用模板方法虽然我不确定为什么那里有一个static_cast&lt;&gt;...

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

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

      【讨论】:

        【解决方案5】:

        Vividos 已经回答了帖子的主要部分。即使我会使用范围运算符而不是更麻烦的 static_cast + 取消引用运算符。

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

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

        【讨论】:

          【解决方案6】:

          已经有几个问题可以解决这个问题。似乎我们已经没有问题要问了。也许搜索框应该比按钮大。

          【讨论】:

          • 我找不到能回答这个特定问题的问题。可以吗?
          • 这并不是因为它是关于多重继承,你可以猜到它已经在其他帖子中讨论过了。他被问到‘我想做的是让 D 以某种方式枚举它在其祖先中拥有的所有 fn() 实例。这是否可能以其他方式表示虚拟功能?尽管我认为这是一个有点幼稚的问题,但您在此处链接的所有问题都没有谈论这样的事情。我认为他的提问非常具体和独特。 -1.
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-07-06
          • 2012-01-31
          • 2014-10-20
          • 1970-01-01
          • 2012-09-20
          • 2014-07-28
          • 1970-01-01
          相关资源
          最近更新 更多