是否需要多重继承?

时间:2015-07-14 14:08:07

标签: c++ inheritance multiple-inheritance object-composition

我遇到如下情况:

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()*/}
};

我在这里考虑多重继承。即,使mixed来自BC。 但是有一些已知的问题(例如, Diamond problem)。

另一种解决方案可能是构图。

但是什么是正确的解决方案,请告知:)

提前致谢!

4 个答案:

答案 0 :(得分:4)

  

一些已知问题(例如,钻石问题)。

首先:除非您明确创建菱形图案,否则没有菱形图案。

 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的细节)。

但是一般的建议是更喜欢构图而不是继承,但这只是一般性的建议细节总是归结为正在解决的实际问题。

答案 1 :(得分:3)

每个方法在调用父项之前必须“执行操作”这一事实会导致问题。

一种解决方案是将IFS=$'\n' A作为B类中的成员。然后,您可以在mixedmixed::f()

中控制您需要执行的操作

如果需要,您可以使用纯虚拟函数mixed::g()base创建基类f()g()可以继承,mixedAB也可以。当你模仿组合时,你可以提到这种可能性。

答案 2 :(得分:3)

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

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

this是一个C ++ 11功能,让编译器检查你实际上是否覆盖。使用它是很好的做法,但如果您的编译器不支持它则不需要。 override是一个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()

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

答案 3 :(得分:1)

以下是虚拟继承的替代方法:使用CRTPBC的功能混合到M中,同时共享公共A没有 vtable的开销。

#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*的函数。