Summary: The case below involves using multiple inheritance for inheriting both an extended interface, and implementation for a basic interface.
I used virtual multiple inheritance to put in place the following system:
I have a hierarchy of abstract interfaces:
ICommon
/ \
ISpecific1 ISpecific2
As expected, the specific interfaces add functionality over the common functionality derived from ICommon.
I also have classes implementing the interfaces:
- CommonImp (implements the common functionality - ICommon)
- SpecificImp1 (implementing ISpecific1)
- SpecificImp2 (implementing ISpecific2)
The application eventually uses only SpecificImp1 and SpecificImp2. But CommonImp is required in order to avoid implementing ICommon twice (in SpecificImp1, SpecificImp2).
This means that, e.g., SpecificImp1 needs to inherit ISpecific1 for the whole interface it needs to expose, and CommonImp for the implementation of the common part. But both ISpecific1 and CommonImp inherit ICommon (one in order to extend the interface, and one for implementation). Which makes SpecificImp1 inherit indirectly twice from ICommon. Using virtual inheritance handled the diamond inheritance issue.
This is the minimal reproducible code example:
#include <iostream>
struct ICommon
{
virtual void DoCommon() = 0;
};
struct ISpecific1 : public virtual ICommon
{
virtual void DoSpecific1() = 0;
};
struct ISpecific2 : public virtual ICommon
{
virtual void DoSpecific2() = 0;
};
struct CommonImp : public virtual ICommon
{
virtual void DoCommon() override { std::cout << "CommonImp::DoCommon" << std::endl; }
};
struct SpecificImp1 : public ISpecific1, public CommonImp
{
virtual void DoSpecific1() override { std::cout << "SpecificImp1::DoSpecific1" << std::endl; }
};
struct SpecificImp2 : public ISpecific2, public CommonImp
{
virtual void DoSpecific2() override { std::cout << "SpecificImp2::DoSpecific2" << std::endl; }
};
int main()
{
SpecificImp1 s1;
s1.DoCommon();
s1.DoSpecific1();
SpecificImp2 s2;
s2.DoCommon();
s2.DoSpecific2();
return 0;
}
This design serves me well so far, but it's quite cumbersome. And multiple inheritance is always something you should consider alternatives for.
So, my question is, given this system, can you suggest a good alternative?
BTW - All types above are structs with everything public. This was done to make the code shorter, please ignore it.
As modification of point 1 in your answer, you do not have to make
CommonImp
a member nor duplicate anything:This solves the general requirement. No virtual inheritance for the specific interfaces needed, no diamond inheritance.
However, in the code above, it is not enforced by the specific interfaces that the specific implementations also have to provide the
ICommon
interface. But it is indirectly enforced by various usages: By callingDoCommon()
in 1. and 2. and by converting the address ob the specific objects toICommon*
in 3. and 4. So the simplest solution would be to leave it at that.We can (to a degree) enforce the inheritance of
ICommon
even within the specific interfaces by demanding a member functiongetICommonP
, which returns anICommon
pointer (see code below). The specific implementations inheriting fromICommon
would just returnthis
. The compiler would throw an error, if this abstract member function is not implemented. This function has the added benefit of making it possible to convert from a specific interface to the common interface (5. and 6.). The specific implementation can be directly converted (3. and 4.), because it inherits fromICommon
. See lines marked with '// added'.A more natural member function would be the conversion operator
virtual operator ICommon&()
within the interfaces instead ofvirtual ICommon* getICommonP()
, but some compilers deliver a warning, when your implementations also inherit from the same class, because the conversion to references to base classes is done automatically in this case without calling the explicit conversion operator member function.On a side note: The specific interfaces are compatible with the solution in point 1 in your answer (except the common interface forwarding): Instead of a pointer to the class itself ('this') the
getICommonP()
functions would return a pointer to theICommonImp
member. The implementation could decide, how to implement and provide the common interface - inheriting from it or keeping the common implementation as member variable.If you do not like an added member function, an alternative for enforcing inheritance of the specific implementations from the interface
ICommon
within the specific interfaces would be to make the specific interfaces into templates and inheriting from them with:Then
template<class T> Ispecific2<T>
could test (or require), whetherT
inherits fromICommon
and fromISpecific2<T>
.This would go into the direction of point 3 in your answer.
As you now have not one
Ispecific2
, but manyISpecific2<>
(because of it being a template) - and you often need one interface, you could have all the templated classesISpecific2Templ<>
inherit from the same actual specific interfaceISpecific2
.