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()
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.
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()
.