Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ why does virtual inheritance allow for the prevention of further inheritance?

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 :)

like image 509
jsdw Avatar asked Apr 29 '13 21:04

jsdw


People also ask

Why do we use virtual in inheritance?

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).

Why virtual classes are important in the case of multiple inheritance?

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.

How does virtual inheritance solve the diamond problem?

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.

How does virtual inheritance work?

Virtual inheritance is a C++ technique that ensures only one copy of a base class's member variables are inherited by grandchild derived classes.


1 Answers

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.

like image 128
dyp Avatar answered Nov 16 '22 23:11

dyp