Related: Does "virtual base class in the case of multilevel inheritance" have significance
I have a template class that can be inherited from in order to impart some select functionality. However, it wants to prevent any classes from further inheriting from anything that inherits it.
The following seems to achieve this:
template<typename Child>
class SealingClass
{
public:
/*public methods etc*/
private:
SealingClass() {}
friend Child;
};
//simplify a bit:
#define Seal( x ) public virtual SealingClass< x >
Now, I can inherit from the above class, as follows:
class NewClass: Seal(NewClass) {};
And if I then try inheriting again from NewClass
, as in:
class AnotherClass: public NewClass {};
and then make an instance of said class:
AnotherClass a;
I get the desired error, regarding the constructor in SealingClass
being private.
So, everything works as I'd like!
However, I have noticed that if I remove the virtual
keyword from the define..
#define Seal( x ) public SealingClass< x >
..my instantiation of AnotherClass
now works just fine.
I understand that the virtual
keyword, in this context, means that only one instance of the base class is defined in cases of multiple inheritance (eg diamond inheritance) where multiple instances of it could exist, leading to ambiguous function calls etc.
But, why does it affect the functionality of the above?
Thanks :)
Virtual inheritance is used when we are dealing with multiple inheritance but want to prevent multiple instances of same class appearing in inheritance hierarchy. From above example we can see that “A” is inherited two times in D means an object of class “D” will contain two attributes of “a” (D::C::a and D::B::a).
Virtual base classes offer a way to save space and avoid ambiguities in class hierarchies that use multiple inheritances. When a base class is specified as a virtual base, it can act as an indirect base more than once without duplication of its data members.
The Diamond Problem is fixed using virtual inheritance, in which the virtual keyword is used when parent classes inherit from a shared grandparent class. By doing so, only one copy of the grandparent class is made, and the object construction of the grandparent class is done by the child class.
Virtual inheritance is a C++ technique that ensures only one copy of a base class's member variables are inherited by grandchild derived classes.
If use virtual inheritance, the most-derived type has to do the initialization of this virtual base class. If you don't use virtual inheritance, the directly derived type has to do the initialization.
Therefore, the private ctor does not prevent the derived type NewClass
from initializing the direct base class SealingClass
, and AnotherClass
does not have to initialize NewClass
if it's not been virtually inherited.
Some examples:
template<typename Child>
class SealingClass {
public: // for now
SealingClass() {}
};
class NewClass : public SealingClass<T> {
public:
NewClass() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// direct base class
};
class AnotherClass : public NewClass {
public:
AnotherClass() : NewClass() {} // allowed, NewClass is a
// direct base class
AnotherClass() : SealingClass<T>() {} // not allowed, SealingClass<T> is
// no direct nor a virtual base class
};
class NewClass_v : public virtual SealingClass<T> {
public:
NewClass_v() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// direct base class
};
class AnotherClass_v : public NewClass_v {
public:
AnotherClass_v() : NewClass_v() {} // allowed, NewClass_virt is a
// direct base class
AnotherClass_v() : SealingClass<T>() {} // allowed, SealingClass<T> is a
// virtual base class
};
Now, if the ctor of SealingClass
is private, AnotherClass_virt
is not allowed to call this ctor due to the private
access specifier and not being a friend.
If you leave out the explicit initialization of a base class (whether virtual or direct), it is default-initialized ([class.base.init]/8), that is, the default ctor is called implicitly (but you still must have access to the ctor, so it's the same as explicitly writting the call to the default ctor).
Some quotes:
[class.base.init]/1
In the definition of a constructor for a class, initializers for direct and virtual base subobjects and non-static data members can be specified by a ctor-initializer
[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.
[class.base.init]/10
In a non-delegating constructor, initialization proceeds in the following order:
- First, and only for the constructor of the most derived class, virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.
- Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
Emphasis mine.
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