19

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

    A
  /   \
 B     C
  \   /
    D

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

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

4

6 回答 6

21

fn除非您在 中再次覆盖D,否则这是不可能的。因为在 D 对象中没有最终覆盖器: BothCBoverride 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描述了一个很好的方法。它可能会帮助您决定您的设计。

于 2009-03-05T20:48:46.373 回答
4

第一个问题,是的,B和C可以定义fn()为虚函数。B::fn()其次,D当然可以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 个或更多模板参数的版本扩展我上面的示例,但我猜类模板参数的数量存在上限(内部编译器)。

于 2009-03-05T20:08:56.657 回答
2

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

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

如果您真的担心换入一个新类,E而不是say BwhereE不下降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()

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

于 2009-03-05T21:01:54.533 回答
2

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

于 2009-03-05T20:09:18.560 回答
0

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

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

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

于 2009-03-05T20:48:11.167 回答
-1

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

于 2009-03-05T20:06:10.923 回答