DeepTrainer algorithm hierarchy and things you’ve never known about diamonds

I did a complete overhaul for the algorithmic structure of the library because when I first wrote it I kept extending an existing structure as new ideas came without being concerned about code redundancy. So now I decided to get rid of all redundant code pieces in all my classes.

I ended up with a nice hierarchy that is only possible in C++, no other languages would allow you to create anything even remotely similar to this. The reason is that other languages (like C# or Java) are not trusting the developers enough to allow them to play with memory, so they are missing a feature called multiple inheritance.

Multiple inheritance can lead to a so called Diamond Problem – a favourite interview question of many C++ interviews. However, in real life scenarios it is quite rare that you will encounter this problem because in modern architectures object composition through dependency injection is favoured to inheritance. I was also considering object composition instead, but this hierarchy clicks together so nicely I really did not want to ruin it. Also a major point against object composition in my case is that the intermediate objects are mostly abstract classes, which means you can’t just instantiate objects from them. I could have made them instantiatable, but even then they would remain intermediate building blocks, completely unusable on their own. In the context of these algorithms the smallest granularity of composition that makes sense is one algorithm. So I think class hierarchy was a better choice here.

class diagram.png

So, I ended up creating a hierarchy that not only has one diamond shapes, but immediately two! This is the price of not having one piece of code repeated anywhere, still being able to implement 5 different algorithms with it. Ok, so what is the diamond problem?

The Diamond Problem

Class inheritance allows developers to extend the capabilities of one class by inheriting from it, and adding more capabilities in the child class. What are these capabilities we are talking about? Code and data. Code in the form of member functions, data in the form of member variables. If you have a chain of inheritance through many classes, at instantiation each base class is created as separate objects with their own pieces of code and pieces of data, thus forming a composite child class in the end. However there is a situation that creates ambiguity when creating a final child composition. Let’s say that class A is a base class with data members. Then let classes B and C both inherit from class A. After this let’s create a class D that inherits from both classes B and C. At the instantiation of class D we run into ambiguity: if both B and C classes implement all data members of class A, then the data members of class A will appear twice in class D, because it inherits from both B and C! This scenario is not allowed, and C++ has a solution for this called virtual inheritance.

Let’s say classes B and D both use virtual inheritance when inheriting from class A. In such a case the compiler will know that in case it encounters the diamond problem it should only take into account the common base class A once. This solves the diamond problem and developers can remain happy. (Or can they?)

Constructors and virtual inheritance

Virtual inheritance is often demonstrated with oversimplified classes that don’t do anything complex and which all have their own default constructors. But when you have more complex classes that require initialisation through non-default constructors you face another challenge brought to you by virtual inheritance. If class A needs to be initialised by a non-default constructor, then you will have to initialise class A too in the constructor initialisation list of class D! I have to admit that this was even a new thing to me but it completely makes sense. When classes B and C are inheriting through virtual inheritance, at instantiation the compiler has to choose which should initialise the constructor of A – and instead of arbitrarily picking one constructor from B or C classes – which are ambiguous in this situation – it expects the developer to explicitly call the constructor of class A from the lowermost child in the hierarchy that is not using virtual inheritance.





Leave a Reply

Your email address will not be published. Required fields are marked *