Click here to Skip to main content
15,065,266 members
Articles / Programming Languages / C++
Tip/Trick
Posted 2 Sep 2015

Stats

19.8K views
79 downloads
19 bookmarked

The Virtual Inheritance and Funny Tricks

Rate me:
Please Sign up or sign in to vote.
4.86/5 (16 votes)
2 Sep 2015CPOL6 min read
The virtual inheritance helps to solve certain class design problems even if they are unrelated to the "deadly diamond of death".

Introduction

To solve the diamond problem, the virtual inheritance in C++ changes the way in which the derived class constructor calls its base class constructors. This “side effect” can be useful for some unorthodoxical class implementations.

The Virtual Inheritance Quick Recap

As we all remember, the goal of the virtual inheritance is solving the ambiguous hierarchy composition problem (aka the "diamond problem") of the multiple inheritance in C++. See, for example: FAQ Where in a hierarchy should I use virtual inheritance? Let us now recall some interesting details.

Consider the following class definitions:

C++
class Base
{
public:
  Base(int n) : value(n)
  { 
    std::cout << "Base(" << n << ")"<< std::endl; // Prints the passed value: Base(N)
  }
  Base() : value(0)
  { 
    std::cout << "Base()"<< std::endl; // No passed value: Base()
  }
 ~Base() { std::cout << "~Base()"<< std::endl; }

 int value;
};

class One : public Base
{
public:
  One() : Base(1) 
  { 
    std::cout << "One()"<< std::endl; 
  }
 ~One() { std::cout << "~One()"<< std::endl; }
};

class Two : public Base
{
public:
  Two() : Base(2)
  { 
    std::cout << "Two()"<< std::endl; 
  }
 ~Two() { std::cout << "~Two()"<< std::endl; }
};

class Leaf : public One, public Two
{
public:
  Leaf() : { std::cout << "Leaf()"<< std::endl; }
 ~Leaf() { std::cout << "~Leaf()"<< std::endl; }
};

In this implementation, instances of the class Leaf bear inside two independent copies of the class Base: the first one comes via the class One, and another – via the class Two. These clones make the expression:

C++
Leaf lf;
lf.value = 0; // will cause compilation error

ambiguous. We must instead use expressions with explicit path specification: either lf.One::value = 0 or lf.Two::value = 0

Normally, we would like to avoid the duplicated Base class sub-objects within one Leaf object.
It can be done by using the virtual inheritance: we need to add the virtual keyword in the inheritance specification of both classes: class One : public virtual Base… and class Two : public virtual Base….

Because of the virtual keyword, the constructor of the Leaf class will call the constructor of the Base only once, and only one instance of the Base class will be created. The Leaf object will have only a single Base sub-object within it (which also will be "shared" by the One and Two sub-objects). It is just what we needed.

The question then arises, “how the compiler knows what parameter to pass to the Base’s constructor?” Indeed, we have two options: the constructor of the class One invokes Base(1), while the constructor of the class Two invokes Base(2). Which one to choose?

The answer is obvious: neither. Instead of calling Base(1) or Base(2), the compiler just uses the default constructor Base() called by the Leaf constructor directly. The printout in our example is:

C++
Base() // NOTE: Neither "Base(1)" nor "Base(2)"
One()
Two()
Leaf()

The compiler implicitly adds the Base() call to the Leaf initialization list and ignores all other calls to the Base constructors. As a result, the effective initialization list looks like:

C++
class Leaf : public Base(), public One, public Two
{
  ...
}

Of course, the Base(...) can be added to the Leaf initialization list explicitly, for example, we can pass the value 3 to the constructor:

C++
class Leaf : public Base(3), public One, public Two
{
  ...
}

In the last case, the output would be:

C++
Base(3)
One()
Two()
Leaf()

In this story, it is important that the Leaf’s constructor calls to Base’s constructor directly as opposed to the indirectly calling (via One and Two constructors) for the non-virtual inheritance.
The fact that the Leaf’s constructor calls to Base’s constructor directly, bypassing the One and Two constructors, can be useful for class system design.

Having completed the recap, we can now consider couple of interesting examples. The first example is the well-known “Final Class” problem.

The Final Class

The “final class” is a class that can be instantiated on both stack and heap but cannot be derived from. In other words, this code is legal:

C++
Final fd;
Final *pd = new Final();

But the following code causes a compilation error:

C++
class Derived : public Final{};

Long time before the final keyword was finally introduced in the C++11 standard, the “Final Class” problem got its solution using the virtual inheritance. See, for example: More C++ Idioms/Final Class.

The solution looks like:

C++
class Seal
{
  friend class Final;
  Seal() {}
};

class Final : public virtual Seal
{
public:
  Final() {}
};

Attempts to derive from the Final class will cause compilation error "cannot access private member of class Seal":

C++
class Derived : public Final
{
public:
  Derived() {} // Will cause compilation error:
               // cannot access private member of class Seal
};

The trick here is that due to the virtual inheritance, the Derived’s constructor must call the Seal’s constructor directly, not via Final’s constructor.
But it is not allowed because the Seal class has only private constructor and the Derived class, unlike the Final class, is not a friend of the Seal class.
The Final class itself is allowed to call the Seal’s private constructor because it is on the friend list of the Seal class. This is why an object of type Final can be created on both stack and heap.

The whole solution is possible because of the virtual inheritance. Just remove the word virtual from the inheritance definition class Final : public virtual Seal and the Derived class will be able to call the Seal’s constructor indirectly via constructor of the Final class which is a friend of the Seal class, and all the magic will be gone.

The second interesting example is another well-known problem - the “Calling Virtual Functions from Constructors” problem.

Calling Virtual Functions from Constructors

We all know that virtual functions should not called from constructors. See, for example: FAQ When my base class’s constructor calls a virtual function on its this object, why doesn’t my derived class’s override of that virtual function get invoked?

The reason why the virtual functions are banned from constructors is that the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Before invoking a virtual function, we must wait until the constructor finished the object creation.

Fortunately, there is a simple way to invoke a code immediately after a function call. Consider the code:

C++
f(const caller_helper& caller = caller_helper())
{
...
}

Compiler creates a temporary object caller_helper before calling the function f() and destroys it after the call. It means that the destructor ~caller_helper() is invoked just after the function f() is completed. That is exactly what we need.

The solution looks like:

C++
class base;

class caller_helper
{
public:
  caller_helper() : m_p(nullptr) {}
  void init(base* p) const { m_p = p; }
  ~caller_helper();
private:
  mutable base* m_p;
};

class base
{
public:
  base(const caller_helper& caller) 
  {	
    caller.init(this); // store the pointer
  }
  virtual void to_override(){} // empty virtual function
};

class derived : public base
{
public:
  derived(const caller_helper& caller = caller_helper()) : base(caller) {}
  virtual void to_override()
  {
    std::cout << "derived"<< std::endl;
  }
};

class derived_derived : public derived
{
public:
  derived_derived(const caller_helper& caller = caller_helper()) : derived(caller) {}
  virtual void to_override()
  {
    std::cout << "derived_derived"<< std::endl;
  }
};

caller_helper::~caller_helper()
{
  if(m_p) m_p->to_override();
}

The constructor of the base class registers the pointer this into the caller_helper object.

C++
base(const caller_helper& caller) 
{	
  caller.init(this); // store the pointer m_p = p;
}

The destructor ~caller_helper() calls the virtual function:

C++
caller_helper::~caller_helper()
{
  if(m_p) m_p->to_override();
}

And the default parameter const caller_helper& caller = caller_helper() at the derived class’s constructor parameter lists:

C++
...
 derived_derived(const caller_helper& caller = caller_helper()) : derived(caller) {}
...
 derived(const caller_helper& caller = caller_helper()) : base(caller) {}

provides the desirable scope – the destructor ~caller_helper() will be invoked immediately after the corresponding derived class’s constructor exit.

The code:

C++
derived td;
derived_derived tdd;

will print out:

C++
derived
derived_derived

The solution works but it has a significant flaw.
Indeed, to call a virtual function after the constructor completion, we must place the default parameter const caller_helper& caller = caller_helper() into the constructor parameter list, and we must repeat it for every derived class in the hierarchy, in our example: derived() and derived_derived().
It is hard to miss for the derived class because its parent class base has no default constructor. But it is easy to forget for the derived_derived class because its base class derived has the default constructor.

And here the virtual inheritance helps us again. We can define class derived : public virtual base and force all the derived classes to call the constructor base(const caller_helper& caller)directly.

After the final touch, we have:

C++
class base;

class caller_helper
{
public:
  caller_helper() : m_p(nullptr) {}
  void init(base* p) const { m_p = p; }
  ~caller_helper();
private:
  mutable base* m_p;
};

class base
{
public:
  base(const caller_helper& caller) 
  {	
    caller.init(this); // store the pointer
  }
  virtual void to_override(){} // empty virtual function
};

class derived : public virtual base
{
public:
  derived(const caller_helper& caller = caller_helper()) : base(caller) {}
  virtual void to_override()
  {
    std::cout << "derived"<< std::endl;
  }
};

class derived_derived : public derived
{
public:
  derived_derived(const caller_helper& caller = caller_helper()) : base(caller) {}
  virtual void to_override()
  {
    std::cout << "derived_derived"<< std::endl;
  }
};

caller_helper::~caller_helper()
{
  if(m_p) // important not to call to not initialized
    m_p->to_override();
}

Conclusions

In the conclusion, I would like to summarize the ideas mentioned above.

  • The virtual inheritance forces the derived classes to call the virtual base class constructor directly as opposed to the indirectly calling (via the chain of constructors) for the non-virtual inheritance.
  • Using this feature, together with the private member access control, we can implement the Final Class pattern. Of course, after the keyword final was introduced into the C++11 standard, it is not of current importance, but it is still a nice pattern.
  • If a function has a default by-value class parameter, than its instance will be created before the function call, and it will be destroyed after the function completion. Using this idea, we can invoke a virtual function after the class constructor finished its job and the class virtual table is built. This guarantees us that the proper virtual function will be invoked. Together with the virtual inheritance, it allows us to implement a nice pattern for calling virtual functions “within” constructor.

History

  • 09/02/2015: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

cocaf
Software Developer (Senior)
Canada Canada
No Biography provided

Comments and Discussions

 
Questionthanks Pin
Member 118606994-Sep-15 14:27
MemberMember 118606994-Sep-15 14:27 
AnswerRe: thanks Pin
cocaf4-Sep-15 21:02
professionalcocaf4-Sep-15 21:02 
SuggestionFinal Pin
fabklein064-Sep-15 9:48
Memberfabklein064-Sep-15 9:48 
GeneralRe: Final Pin
cocaf4-Sep-15 10:07
professionalcocaf4-Sep-15 10:07 
SuggestionMy vote of 5 Pin
Loic URIEN4-Sep-15 8:29
MemberLoic URIEN4-Sep-15 8:29 
GeneralRe: My vote of 5 Pin
cocaf4-Sep-15 10:05
professionalcocaf4-Sep-15 10:05 
GeneralRe: My vote of 5 Pin
Loic URIEN4-Sep-15 10:08
MemberLoic URIEN4-Sep-15 10:08 
Yep, but I should have been more careful, I didn't see that Emre Ataseven already pointed that out (I edited my comment Smile | :) ). So, only the first is relevant.
Anyway, I want to say again that it is a great article, even with few typos like these Smile | :)
GeneralRe: My vote of 5 Pin
cocaf4-Sep-15 11:10
professionalcocaf4-Sep-15 11:10 
GeneralClean and nice Pin
Emre Ataseven4-Sep-15 6:10
professionalEmre Ataseven4-Sep-15 6:10 
GeneralRe: Clean and nice Pin
cocaf4-Sep-15 10:04
professionalcocaf4-Sep-15 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.