'Double Dreadful Diamond Inheritance issue (alternative solutions allowed)

I ended up in a situation lined out below.

I have one library that is pure CPP without external libraries, and another project that is an SDK to interface with an external library.

Double Dreadful Diamond issue

"I" in front of the name indicates an abstract class. Arrows indicate inheritance.

I have IDevice which contains HandleInput(data) as a callback, and StartDevice(). Then I have a more specific type of device: ISmartwatch (containing StartTimer), and from that inherits a more specific version SmartwatchV1, which implements HandleInput(data) according to its needs.

That all seemed great until I came to the external SDK part, where the library expects me to use inheritance to interface with it to override some functions. So, I have to inherit from the external library, and from my own CPP library, to override the functions I need. Most of these library overrides suit any device (IExternalLibDevice), but a few are specific to the exact Stopwatch version (ExternallSmartWatchV1).

Then for polymorphism in my SDK, I would like to call and override functions both provided by the library and my own device example: libDevice.StartDevice() and use library calls within this optionally overriden StartDevice. Or stopWatch.StartTimer(), stopwatchV1.libraryOverride(). The object which I need to create is the green one, however, the white SmartWatchV1 is also an object to instantiate in applications without the library. (And obviously I keep in mind any future alternative devices or stopwatch versions.)

I think if I drop any inheritance arrow, I would either lose out on polymorphism (so SDK code will only work for a very specific smartwatch version), or I cannot override functions I need anymore. Composition would be nice, but won't work for overriding functions, or is there an option I don't know about?

And so, I ended up here. I am encountering quite some annoying errors implementing this, since double diamond is usually solved with virtual inheritance (nice page about double diamond: https://isocpp.org/wiki/faq/multiple-inheritance#mi-diamond). However, when applied here (see the v's that indicate "virtual" in the image), I have one inheritance that should both be virtual and not be virtual. Additionally, virtual inheritance makes constructors really annoying in my generic CPP library. Even without virtual (which as far as I'm aware would cause some duplication of classes in memory and a lot of ambiguity to solve), I have some constructor errors ("no suitable default constructor" for a class that must not have a default constructor, etc) issues.

I have been battling to solve this for a long time, and I hope someone more experienced can make a suggestion that provides a better solution for my code structure or issue.



Solution 1:[1]

In the end, I solved it by using composition:

  • Add an IDevice pointer to IExternalLibDevice that is set in the constructor.
  • In IExternalLibSmartwatch: add an ISmartwatch pointer to the constructor and pass it to its parent constructor. Also, add a function that retrieves the IDevice pointer as an ISmartwatch.
  • In ExternalSmartwatchV1: also add a SmartwatchV1 to the constructor and pass it to its parent constructor, and create a function that retrieves the IDevice pointer as a SmartwatchV1.

The IDevice pointer holds the reference to the cppLibDevice, and can now be cast to any of the subclasses it belongs to. Downside: I cannot override the cpp lib classes, but it was not a hard requirement for my code, since I created alternative functions in the ExternalLib classes that can optionally call the cppLibDevice functions, or completely replace them.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Amber Elferink