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:
class Base
{
public:
Base(int n) : value(n)
{
std::cout << "Base(" << n << ")"<< std::endl; }
Base() : value(0)
{
std::cout << "Base()"<< std::endl; }
~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:
Leaf lf;
lf.value = 0;
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:
Base() 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:
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:
class Leaf : public Base(3), public One, public Two
{
...
}
In the last case, the output would be:
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:
Final fd;
Final *pd = new Final();
But the following code causes a compilation error:
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:
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":
class Derived : public Final
{
public:
Derived() {} };
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:
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:
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); }
virtual void to_override(){} };
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.
base(const caller_helper& caller)
{
caller.init(this); }
The destructor ~caller_helper()
calls the virtual function:
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:
...
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:
derived td;
derived_derived tdd;
will print out:
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:
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); }
virtual void to_override(){} };
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)
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