是否需要多重继承

Is multiple inheritance needed?

本文关键字:多重继承 是否      更新时间:2023-10-16

我有一个这样的情况:

class A {
    virtual void f() { /*some default impl here*/}
    virtual void g() { /*some default impl here*/}
};
class B : public A {
    virtual void f() { /* do some specific stuff and call parent's f()*/}
    virtual void g() { /* do some specific stuff and call parent's g()*/}
};
class C : public A {
    virtual void f() { /* do some specific stuff and call parent's f()*/}
    virtual void g() { /* do some specific stuff and call parent's g()*/}
};
class mixed /* I don't know how to derive*/ {
    virtual void f() { /* call B::f()*/}
    virtual void g() { /* call C::g()*/}
};

我这里考虑的是多重继承。即由BC衍生出mixed。但也有一些已知的问题(例如,钻石问题)。

另一个解决方案是组合。

但正确的解决方法是什么,请告诉我:)

提前感谢!

一些已知的问题(例如,Diamond问题)。

第一:除非你显式地创建一个,否则没有菱形图案。

 class mixed: public B, public C

这将使混合继承B和c,每个都有自己的显式A(没有菱形)。

由于B和C都有从A派生的虚成员,因此应该调用哪一个变得不明确,但您已经通过mixed中所有虚成员的显式定义解决了这个问题(因此问题解决了)。

  void mixed::f() { B::f();} // works fine.

现在即使你显式地创建了一个菱形。

注意:菱形图案不正常出现。菱形模式是一个必须显式做出的设计决策,您可以使用它来解决某些类型的问题(通过使用虚拟继承)。

class B: public virtual A ...
class C: public virtual A ...
class mixed: public B, public C ...

你还是没有问题。因为mixed::f()只使用B分支(然后是A),而mixed::g()只使用C分支(然后是A)。

即使A有自己的状态(虽然这可能是一个坏主意,通常最好使用接口作为虚拟基类),那么我们没有问题,因为mixed::f()mixed::g()只调用一个子函数(问题开始发生,如果他们调用两边,A的状态都被两个调用改变。

另一个解决方案是组合。

也可以。

 class mixed: public A
 {
     B    b;
     C    c;
     public:
         virtual void f() override {b.f();}
         virtual void g() override {c.g();}
     ....
 };

但是正确的解决方案是什么

没有正确的解决方案。这在很大程度上取决于你没有提到的细节(比如a的细节是什么)。

BUT一般建议是选择复合而不是继承,但这只是一般建议的细节,将始终归结为要解决的实际问题。

每个方法在调用父类之前都必须"做一些事情",这是导致问题的原因。

一个解决方案是将AB作为mixed类的成员。然后你可以在mixed::f()mixed::g()

中控制你需要对它们做什么如果需要,可以用纯虚函数f()g()创建一个基类basemixed可以继承它,ABC也可以。当你引用组合
时,你暗示了这种可能性。

我想你可能正在寻找这样的东西。(对不起,这是一大堆代码,但它真的很直接。)

#include <iostream>
struct A
{
  virtual void
  f()
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
  }
  virtual void
  g()
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
  }
  // Don't forget the virtual destructor.
  virtual ~A() noexcept = default;
};
struct B : virtual A
{
  virtual void
  f() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    A::f();
  }
  virtual void
  g() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    A::g();
  }
};
struct C : virtual A
{
  virtual void
  f() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    A::f();
  }
  virtual void
  g() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    A::g();
  }
};
struct D : virtual B, virtual C
{
  virtual void
  f() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    B::f();
  }
  virtual void
  g() override
  {
    std::cout << __PRETTY_FUNCTION__ << 'n';
    C::g();
  }
};
int
main()
{
  D d {};
  d.f();
  std::cout << 'n';
  d.g();
}

override是c++ 11的一个特性,它可以让编译器检查你实际上是否重写了。使用它是一种良好的实践,但如果编译器不支持它,则不需要使用它。__PRETTY_FUNCTION__是一个GCC扩展,用于获取命名当前函数签名的字符串字面值。标准c++有__func__,但在这里用处不大。如果你的编译器没有类似__PRETTY_FUNCTION__的功能,你可以自己键入字符串。

输出:

virtual void D::f()
virtual void B::f()
virtual void A::f()
virtual void D::g()
virtual void C::g()
virtual void A::g()

它可以工作,但我不认为这是漂亮的代码。组合可能是更好的解决方案。

这是虚拟继承的另一种选择:使用CRTP将BC的功能混合到M中,同时共享一个公共A,而没有虚表的开销。

#include <iostream>
struct A
{
    int a;
};
template <typename T>
struct B
{
    A *get_base_a() {return static_cast<T*>(this);}
    void print_a() {std::cout << get_base_a()->a << 'n';}
};
template <typename T>
struct C
{
    A *get_base_a() {return static_cast<T*>(this);}
    void print_a() {std::cout << get_base_a()->a << 'n';}
};
struct M : A, B<M>, C<M>
{
};
int main()
{
    M m;
    m.a = 1;
    m.B::print_a();
    m.C::print_a();
    return 0;
}

但是请注意,您不能将M*传递给期望B*C*的函数。