I want to implement an inheritance hierarchy in my C++ game engine that has some analogies from Java:
Object
class,It's just an analogy that gives me some benefits (not a strict 1:1 Java:C++ mapping which is not possible).
By "interface" I mean here a class with only pure virtual, public methods. I know that it's not a strict and precise definition of it.
So I've made:
class Object{...};
/* interfaces */
class Nameable : public Object{...};
class Moveable : public Object{...};
class Moveable3D : public Moveable{...};
class Moveable2D : public Moveable{...};
class Model3D : public Moveable3D, public Nameable{...}
class Line3D : public Moveable3D{...}
class Level: public Nameable{...}
class PathSolver : public Object{...}
As you can see, Model3D
now inherits from Object
via multiple ways:
Moveable3D -> Moveable -> Object
,Nameable -> Object
.And my compiler in VS2013 (VC++) warns me about it.
Is that an issue?
If so, how to solve it to get a better inheritance structure?
I was thinking about two ways, but both have more disadvantages then advantages:
Nameable
and Object
. But that way Level
would lost its inheritance from Object
too (and my first goal was: all objects inherit from Object
class). : public Object
from each interface. But that way I would be force to manually type :public Object
in each of many engine final classes (Model3D
, Line3D
, Level
, PathSolver
). That way both refactoring and creating new final types would be harder. Moreover the information that each interface will be guaranteed to inherit from Object
is lost that way.I would like to avoid virtual inheritance (one level of abstraction further) if it's not necessary. But maybe it is (at least for : public Object
)?
As said by Juraj Blaho the canonical way to solve this problem is virtual inheritance. One implementation model for virtual inheritance is to add an entry in the vtable of classes using virtual inheritance pointing to the real unique object from the base class. That way, you get the following inheritance graph :
Model3D
/ \
Moveable3D Nameable
| |
Moveable |
\ /
Object
That is you must have :
class Nameable : public virtual Object{...};
class Moveable : public virtual Object{...};
The other classes do not need virtual inheritance
Without virtual inheritance the graph would have been
Model3D
/ \
Moveable3D Nameable
| |
Moveable |
| |
Object Object
with 2 distinct instances of Object
The most tricky part in virtual inheritance is the call of constructors (ref Virtual Inheritance in C++, and solving the diamond problem) Because there is only a single instance of a virtual base class that is shared by multiple classes that inherit from it, the constructor for a virtual base class is not called by the class that inherits from it (which is how constructors are called, when each class has its own copy of its parent class) since that would mean the constructor would run multiple times. Instead, the constructor is called by the constructor of the concrete class. ... By the way, the constructors for virtual base classes are always called before the constructors for non-virtual base classes. This ensures that a class inheriting from a virtual base class can be sure the virtual base class is safe to use inside the inheriting class's constructor. The destructor order in a class hierarchy with a virtual base class follows the same rules as the rest of C++: the destructors run in the opposite order of the constructors. In other words, the virtual base class will be the last object destroyed, because it is the first object that is fully constructed.
But the real problem is that this Dreadful Diamond on Derivation is often considered as a bad hierarchy design and is explicitely forbidden in Java.
But IMHO, what C++ virtual inheritance does under the hood is not that far from what you would have if all your interface classes were pure abstract classes (only pure virtual public methods) not inheriting from Object and if all you implementation classes did inherit explicitely from Object. Using it or not is up to you : after all it is part of the language ...
Use virtual inheritance of interfaces. That will ensure that there is only one instance of base class in the derived classes:
class Object{...};
/* interfaces */
class Nameable : public virtual Object{...};
class Moveable : public virtual Object{...};
class Moveable3D : public virtual Moveable{...};
class Moveable2D : public virtual Moveable{...};
class Model3D : public virtual Moveable3D, public virtual Nameable{...}
class Line3D : public virtual Moveable3D{...}
class Level: public virtual Nameable{...}
class PathSolver : public virtual Object{...}
Without virtual inheritance there are multiple instances of the same base in the object which will cause that you will not be able to substitute the concrete type where the interface is needed:
void workWithObject(Object &o);
Model3D model;
workWithObject(model);
Yes, it is an issue: In the memory layout of Model3D
, there are two subobjects of type Object
; one is a subobject of Movable3D
, the other of Nameable
. As such, these two subobjects may contain different data. This is especially a problem, if the object class contains functionality that has to do with the identity of an object. For instance, an Object
class that implements reference counting must be a unique subobject of all objects; an object that has two different reference counts is very likely a very bad idea...
To avoid this, use virtual inheritance. The only virtual inheritance that is needed is from the Object
base class:
class Objec{...];
class Nameable : public virtual Object{...};
class Moveable : public virtual Object{...};
class Moveable3D : public Moveable{...};
class Moveable2D : public Moveable{...};
class Model3D : public Movable3D, public Nameable{...};
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With