Inherit from multiple partial implementations of a

2019-03-18 05:54发布

问题:

Is it possible to have a number of partial implementations of an abstract interface, and then collect these partial implementations into a single concrete class by using multiple inheritence?

I have the following example code:

#include <iostream>

struct Base
{
    virtual void F1() = 0;
    virtual void F2() = 0;
};

struct D1 : Base
{
    void F1() override { std::cout << __func__ << std::endl; }
};

struct D2 : Base
{
    void F2() override { std::cout << __func__ << std::endl; }
};

// collection of the two partial implementations to form the concrete implementation
struct Deriv : D1, D2
{
    using D1::F1; // I added these using clauses when it first didn't compile - they don't help
    using D2::F2;
};

int main()
{
    Deriv d;
    return 0;
}

This fails to compile with the following errors:

main.cpp: In function ‘int main()’:
main.cpp:27:11: error: cannot declare variable ‘d’ to be of abstract type ‘Deriv’
main.cpp:19:8: note:   because the following virtual functions are pure within ‘Deriv’:
main.cpp:5:18: note:    virtual void Base::F1()
main.cpp:6:18: note:    virtual void Base::F2()

回答1:

Try inheriting virtually from Base:

struct D1 : virtual Base
{
    void F1() override { std::cout << __func__ << std::endl; }
};

struct D2 : virtual Base
{
    void F2() override { std::cout << __func__ << std::endl; }
};

Without the virtual inheritance, your multiple inheritance scenario looks like inheritance from two separate and incomplete base classes D1 and D2, neither of which can be instantiated.



回答2:

Is it possible to have a number of partial implementations of an abstract interface, and then collect these partial implementations into a single concrete class by using multiple inheritence?

Yes.

Each Base base class subobject brings two pure virtual functions. How many of those base subobjects do you want in Deriv?

  • If you want 2 Base base class subobject, Deriv::D1::Base and Deriv::D2::Base (so conversions from Deriv& to Base& would be ambiguous) then you will have 4 different virtual functions in Deriv: Deriv::D1::Base::F1(), Deriv::D1::Base::F2(), Deriv::D2::Base::F1(), Deriv::D2::Base::F2(). Only the first and last ones are implemented, so the two middle ones are pure virtual: Deriv::D1::Base::F2(), Deriv::D2::Base::F1(). You have two entirely independent inheritance relations: Deriv inherits from D1 and Deriv inherits from D2.
  • If you want only one Base base class subobject Deriv::Base, then you will have only 2 different virtual functions in Deriv: Base::F1(), Base::F2().

Non-virtual inheritance in C++ is "concrete" inheritance, like containment: struct D : B means that for each D object there is exactly one B base class subobject, just like struct C { M m; } means that for each C there is exactly one M class member subobject. These relations are one-to-one.

OTOH, virtual inheritance is a more "abstract": struct D : virtual B means that for each D object is associated with a B base class subobject, but this relation is many-to-one, like struct C { M &m; }.

In general, for any derived class D (here Deriv), and for any virtual base B of D (here Base), all the base classes of D virtually derived from B (here Deriv::D1, Deriv::D2) contribute to the overriding of the virtual functions in the base class:

  • Base::F1() is overridden by Deriv::D1::F1()
  • Base::F2() is overridden by Deriv::D2::F2()

Non-virtual and virtual inheritance are very different inheritance relations, just like if non-virtual member functions and a virtual function introduce different relations between functions with the same signature in derived and base classes (hiding vs. overriding).

Like virtual and non-virtual functions, virtual and non-virtual base classes must be used in different situations (and one is not "better" than the other in general).

How to "fix" your code without virtual inheritance?

struct Deriv : D1, D2
{
    using D1::F1; // I added these using clauses when it first didn't compile - they don't help
    using D2::F2;
};

Yes: using declaration control name-lookup, so they affect visibility and ambiguity issues, not virtual functions overriding.

With your original non-virtual inheritance based design, in order to make Deriv a concrete class, you would have to explicitly implement both F1() and F2() virtual function signatures (there are 4 virtual functions in Deriv, but with only 2 different signatures), so you need 2 function definitions:

struct Deriv : D1, D2
{
    void F1() override { D1::F1(); }
    void F2() override { D2::F2(); }
};

Note that Deriv::F1() overrides Deriv::D1::F1() and Deriv::D2::F1().