Reputation: 4043
I want to implement a class hierarchy in C++:
Simplified I have this code:
#include <iostream>
class IClass {
public:
virtual int commonMethod() const = 0;
};
class Class : public virtual IClass {
protected:
int commonValue;
public:
Class(int commonValue) : commonValue(commonValue) {}
virtual int commonMethod() const {
return commonValue;
}
};
class IClassDerived : public virtual IClass {
public:
virtual void specialMethod() = 0;
};
class ClassDerived : public Class, public virtual IClassDerived {
public:
ClassDerived(int commonValue) : Class(commonValue) {}
virtual void specialMethod() {
// do something
}
};
class IClassDerived2 : public virtual IClassDerived {
public:
virtual void specialMethod2() = 0;
};
class ClassDerived2 : public ClassDerived, public virtual IClassDerived2 {
public:
ClassDerived2(int commonValue) : ClassDerived(commonValue) {}
virtual void specialMethod2() {
specialMethod();
}
};
class IClassDerived3 : public virtual IClassDerived2 {
public:
virtual int commonMethod() const override = 0;
};
class ClassDerived3 : public ClassDerived2, public virtual IClassDerived3 {
public:
ClassDerived3(int commonValue) : ClassDerived2(commonValue) {}
virtual int commonMethod() const override {
return 4711;
}
};
int main() {
ClassDerived foo(1);
ClassDerived2 foo2(2);
ClassDerived3 foo3(3);
std::cout << foo.commonMethod() << " " << foo2.commonMethod() << " " << foo3.commonMethod() << " " << std::endl;
// 1 2 4711
return 0;
}
I now have two questions:
Upvotes: 3
Views: 3551
Reputation: 39121
I have recently found a workaround that does not need virtual inheritance (see below).
Basically, the language requires the use of virtual inheritance in this case to solve the problem directly since you inherit multiple times from the same class. Without virtual inheritance, you'll end up with this:
Interface0 Interface0 Interface0 ^ ^ ^______ | | \ Interface1 Interface1 Impl0 ^ ^__________________ ^ | \ | Interface2 Impl1 ^______________________________ ^ \ | Impl2
There are multiple "instances" of the InterfaceX
base class, which are independent. Consider the Interface0
instance from the path Impl1 -> Interface1 -> Interface0
. The Impl0
class does not inherit from that instance of Interface0
, hence it does not implement its virtual functions. Note that this is useful if all these interface classes were stateful classes (with data members) instead of pure interfaces.
But in this particular situation, where you only inherit from an interface, virtual inheritance isn't required in theory. We want to get to the following picture:
Interface0 _ ^ |\ | \ Interface1 _ Impl0 ^ |\ ^ | \ | Interface2 _ Impl1 |\ ^ \ | Impl2
Theoretically, Impl1
could define one single vtable with the entries from Impl0
implementing the virtual functions from Interface0
, and the functions from Impl1
implementing the virtual functions from Interface1
. The result would be a single vtable with no offset computations required (hence no need for virtual inheritance).
But, alas, the language doesn't define inheritance this way -- it doesn't make a difference between abstract classes and pure interfaces. It lets you only override virtual functions through sideway-inheritance (Impl0
overriding virtual functions of Impl1 -> Interface1 -> Interface0
) if you inherit them via virtual inheritance. By inheriting virtually, you specify that you indeed inherit only once from Interface0
, so both paths from Impl1
to Interface0
(direct inheritance and via Impl0
) yield the same class.
Virtual inheritance has several drawbacks, since it must allow cases where the location of the base class (relative to a subobject) can only be determined at run-time. However, there's a workaround that doesn't need virtual inheritance.
It is often more useful to write a class first self-contained, and then adapt it to an interface. The interface is typically defined by the surrounding architecture, and hence not necessarily general for that class. By separating the interface from the implementation, you allow reuse of the implementation with a different interface.
If we put this together: We cannot use multiple inheritance to implement virtual functions side-ways, and we want to separate interface from implementation. We end up with: Either, we don't let our implementation derive from anything (this leads to boilerplate code) or we derive linearly, the only inheritance from an interface at the top.
By writing our implementation classes as class templates, we can derive linearly and pass the top interface to be derived from up to the base implementation class:
struct Interface0 {
virtual void fun0() = 0;
};
struct Interface1 : Interface0 {
virtual void fun1() = 0;
};
struct Interface2 : Interface1 {
virtual void fun2() = 0;
};
template<typename Interface = Interface0>
struct Impl0 : Interface {
void fun0() {}
};
template<typename Interface = Interface1>
struct Impl1 : Impl0<Interface> {
void fun1() {}
};
template<typename Interface = Interface2>
struct Impl2 : Impl1<Interface> {
void fun2() {}
};
int main()
{
auto x = Impl2<Interface2>(); // or simply: Impl2<>()
Interface2* p = &x;
}
Note that we use inheritance for both: implementing Impl1
in terms of Impl0
, and passing the extended interface to our base class.
The case in main
, Impl2<>
:
Interface0 ^ | Interface1 ^ | Interface2 _ |\ \ Impl0<Interface2> ^ | Impl1<Interface2> ^ | Impl2<Interface2>
Another case, Impl1<>
:
Interface0 ^ | Interface1 _ |\ \ Impl0<Interface1> ^ | Impl1<Interface1>
Upvotes: 7
Reputation: 41301
You have to initialize virtual base classes in all derived class constructors. Thus, if you had
class ClassDerived : public virtual Class, public virtual IClassDerived {
// ^^^^^^^
Then in the constructors of ClassDerived2
and ClassDerived3
you would have to initialize Class
despite that it seems to be initialized in the constructor of ClassDerived
:
ClassDerived2(int commonValue) : Class(commonValue), ClassDerived(commonValue) {}
ClassDerived3(int commonValue) : Class(commonValue), ClassDerived2(commonValue) {}
The reason for this is [class.base.init]/7
:
A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
Regarding whether there is a better way, personally I'd rather use aggregation and no virtual inheritance, though it requires writing some boilerplate forwarding code:
class IClass {
public:
virtual int commonMethod() const = 0;
};
class Class : public IClass {
protected:
int commonValue;
public:
Class(int commonValue) : commonValue(commonValue) {}
virtual int commonMethod() const {
return commonValue;
}
};
class IClassDerived : public IClass {
public:
virtual void specialMethod() = 0;
};
class ClassDerived : public IClassDerived { // no inheritance from Class
public:
ClassDerived(int commonValue) : m_class(commonValue) {}
virtual int commonMethod() const {
return m_class.commonMethod();
}
virtual void specialMethod() {
// do something
}
private:
Class m_class;
};
// and so on
Upvotes: 2