C++ 多重继承+虚函数一塌糊涂

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/616380/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 16:17:06  来源:igfitidea点击:

Multiple inheritance + virtual function mess

c++multiple-inheritancevirtual-functionsdiamond-problem

提问by shoosh

I have a diamond multiple inheritance scenario like this:

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

    A
  /   \
 B     C
  \   /
    D

The common parent, A, defines a virtual function fn().
Is it possible for both B and C to define fn()?
If it is, then the next question is - can D access both B and C's fn() without disambiguation? I'm assuming there is some syntax for this..
And is it possible for D to do that without knowing specifically who are B and C? B and C can be replaces by some other classes and I want the code in D to be generic.

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

What I'm trying to do is to have D somehow enumerate all of the instances of fn() it has in its ancestry. Is this possible in some other means that virtual functions?

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

回答by Johannes Schaub - litb

Unless you overwrite fnagain in D, no it is not possible. Because there is no final overrider in a D object: Both Cand Boverride A::fn. You have several options:

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

  • Drop either C::fnor B::fn. Then, the one that still overrides A::fnhas the final overrider.
  • Place a final overrider in D. Then, that one overrides A::fnaswell as fnin Cand B.
  • 删除C::fnB::fn。然后,仍然覆盖的那个A::fn具有最终覆盖。
  • 在 D 中放置一个最终覆盖。然后,该覆盖A::fn以及fnC和 中B

For example the following results in a compile time error:

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

#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;
}

You can, however derive non-virtual from A in C and B, but then you have no diamond inheritance anymore. That is, each data-member in A appears twice in B and C because you have two A base-class sub-objects in an D object. I would recommend you to rethink that design. Try to eliminate double-objects like that that require virtual inheritance. It often cause such kind of conflicting situations.

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

A case very similar to this is when you want to override a specific function. Imagine you have a virtual function with the same name in B and C (now without a common base A). And in D you want to override each function but give different behavior to each. Depending whether you call the function with a B pointer or C pointer, you have the different behavior. Multiple Inheritance Part IIIby Herb Sutter describes a good way of doing that. It might help you decide on your design.

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

回答by vividos

First question, yes, B and C can define fn()as a virtual function. Second, D can of course access B::fn()and C::fn()by using the scope operator :: Third question: D must at least know B and C, since you have to define them on the inheritance list. You can use templates to let the types of B and C open:

第一个问题,是的,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();

About the wish to automatically enumerate all fn() functions of all classes that D inherits from: I'm not sure if that is possible without resorting to MPL. At least you can extend my example above with versions that deal with 3 and more template parameters, but I guess there is an upper (internal compiler-)limit of number of class template parameters.

关于自动枚举 D 继承自的所有类的所有 fn() 函数的愿望:我不确定不求助于 MPL 是否可行。至少您可以使用处理 3 个或更多模板参数的版本扩展我上面的示例,但我想类模板参数的数量有一个上限(内部编译器)。

回答by joelr

You cannot enumerate the definitions of fn() in the ancestry. C++ lacks reflection. The only way I can imagine is a giant loop testing the typeid's of all possible ancestors. And it hurts to imagine that.

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

回答by D.Shawley

You might want to look at LokiTypeLists if you really need to be able to track ancestry and enumerate through types. I'm not sure if what you are asking for is really possible without a bunch of work. Make sure that you aren't over-engineering here.

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

On a slightly different note, if you are going to use MI in this manner (i.e., the dreaded diamond), then you should be very explicit about which virtual member you want. I can't think of a good case where you want to choose the semantics of B::fn()over C::fn()without explicitly making a decision when writing D. You will probably pick one over the other (or even both) based on what the individual method does. Once you have made a decision, the requirement is that inherited changes do not change the expectations or semantic interface.

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

If you are really worried about swapping in a new class, say Ein place of say Bwhere Edoes not descend from Bbut offers the same interface, then you should really use the template approach though I'm not sure why there is a static_cast<>in there...

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

works fine and seems a little easier to look at.

工作正常,似乎更容易看。

回答by Scottie T

There are already several questions that deal with this. Seems like we're running out of questions to ask. Maybe the search box should be bigger than the Ask Question button.

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

See

回答by David Rodríguez - dribeas

Vividos has already answered the main part of the post. Even if I would use the scope operator instead of the more cumbersome static_cast<> + dereference operator.

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

Depending on the task at hand, maybe you can change the inheritance relationship from D to B and C for a less coupling composition (plus possibly inheritance from A). This is assuming that you don't need D to be used polimorphically as either B or C, and that you don't really require B and C sharing the same base instance.

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

If you opt for composition, you can receive the B and C as arguments to your constructor as references/pointers of type A, making D completely unaware of the types B and C. At that point, you can use a container to hold as many A derived objects. Your own implementation of fn() (if you so decide) or any other method.

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