Virtual Inheritance in C++

The C++ language supports the concept of multiple inheritance. This means one class can have multiple base classes. This feature is not available in other languages, such as C# or Java. The designers of these languages didn’t considered the benefits of supporting this feature to worth the effort. And probably one of the reasons is that multiple inheritance may lead to the so-called diamond inheritance problem, when one class derives from two different classes, that in turn derive from the same class. In this article, I will discuss the problem as well as the solution in C++.

The problem

To understand the problem, let’s start with the following class hierarchy:

This is a simple hierarchy with the following classes:

  • control is the base class of all visual elements and has some data members such as id, width, and height
  • image and button are classes derived from control, each with its own additional data members
  • image_button that is both an image and a button and inherits from these two classes, also with its own additional data members

This can be represented in code as follows:

struct control
{
   int id;
   int width;
   int height;
};

struct image : public control
{
   int stretch_style;
   int stretch_direction;
};

struct button : public control
{
   int text_alignment;
};

struct image_button : public image, public button
{
   int content_alignment;
};

The image above shows the inheritance hierarchy, but the object memory layout is different. This actually looks like this:

Object memory layout

What we can see from here is that:

  • image contains everything that control has plus its own data members
  • button contains everything that control has plus its own data members
  • image_button contains everything that image and button has plus its own data members; however, this implies it has two copies of the data members of control.

As a result, trying to access any of the data members from control using an image_button object results in a compiler error.

image i;
i.id = 1;     // OK

button b;
b.id = 2;     // OK

image_button ib;
ib.id = 3;    // error
error C2385: ambiguous access of 'id'
message : could be the 'id' in base 'control'
message : could be the 'id' in base 'control'

In this example, we only had data members but the same problem occurs with member functions.

A slightly modified version of the class hierarchy with a virtual function draw() overridden in each class, and a member function resize() in the control base class.

Memory layout now contains additional a pointer to a virtual table.

struct control
{
   int id;
   int width;
   int height;

   void resize(int const w, int const h, bool const redraw = true) 
   {
      width = w;
      height = h;
      if(redraw)
         draw();
   }

   virtual void draw() 
   { 
      std::cout << "control::draw\n"; 
   }
};

struct image : public control
{
   int stretch_style;
   int stretch_direction;

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "image::draw\n"; 
   }
};

struct button : public control
{
   int text_alignment;

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "button::draw\n"; 
   }
};

struct image_button : public image, public button
{
   int content_alignment;

   virtual void draw() override
   {
      button::draw();
      image::draw();
      std::cout << "image_button::draw\n";
   }
};

int main()
{
   image i;
   i.id = 1;           // OK
   i.resize(32, 32);   // OK

   button b;
   b.id = 2;           // OK
   b.resize(100, 20);  // OK

   image_button ib;
   ib.id = 3;          // error
   ib.resize(100, 20); // error
}

The solution

Here is where virtual inheritance comes to rescue. By declaring a base class as virtual you are ensuring that the memory layout does not duplicate the base class members.

struct control
{
   int id;
   int width;
   int height;
};

struct image : virtual public control
{
   int stretch_style;
   int stretch_direction;
};

struct button : virtual public control
{
   int text_alignment;
};

struct image_button : public image, public button
{
   int content_alignment;
};

Note: the virtual keyword can be use either before or after the access specifier. Therefore virtual public control and public virtual control are equivalent.

The memory layout of the image_button class looks as follows:

From this representation, we can see that:

  • there is no duplication of the data members from the control base class
  • the data members from the control class are present at the end of the layout
  • there is a pointer to a virtual base table for both the image and button classes

With virtual functions added to these classes, the memory layout will also contain a pointer to the virtual function table in the control base class.

struct control
{
   int id;
   int width;
   int height;

   void resize(int const w, int const h, bool const redraw = true) 
   {
      width = w;
      height = h;
      if(redraw)
         draw();
   }

   virtual void draw() 
   { 
      std::cout << "control::draw\n"; 
   }
};

struct image : virtual public control
{
   int stretch_style;
   int stretch_direction;

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "image::draw\n"; 
   }
};

struct button : virtual public control
{
   int text_alignment;

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "button::draw\n"; 
   }
};

struct image_button : public image, public button
{
   int content_alignment;

   virtual void draw() override
   {
      button::draw();
      image::draw();
      std::cout << "image_button::draw\n";
   }
};

However, now we can write the following snippet without getting any more errors:

int main()
{
   image i;
   i.id = 1;           // OK
   i.resize(32, 32);   // OK

   button b;
   b.id = 2;           // OK
   b.resize(100, 20);  // OK

   image_button ib;
   ib.id = 3;          // OK
   ib.resize(100, 20); // OK
}

Construction and destruction of objects

When we have a virtual hierarchy, constructors and destructors are invoked as follows:

  • virtual base classes are constructed before non-virtual base classes; therefore, their constructors are called first in the order they appear in a depth-first, left-to-right traversal of the graph of base classes
  • constructors for the rest of the classes are then called, from base class to derived class
  • destructors are called in the opposite order of construction

Let’s look at the following example:

struct control
{
   int id;
   int width;
   int height;

   control(int const i) :id(i)
   {
      std::cout << "control ctor\n";
   }

   virtual ~control()
   {
      std::cout << "control dtor\n";
   }

   void resize(int const w, int const h, bool const redraw = true) 
   {
      width = w;
      height = h;
      if(redraw)
         draw();
   }

   virtual void draw() 
   { 
      std::cout << "control::draw\n"; 
   }
};

struct image : virtual public control
{
   int stretch_style;
   int stretch_direction;

   image(int const i) :control(i)
   {
      std::cout << "image ctor\n";
   }

   virtual ~image()
   {
      std::cout << "image dtor\n";
   }

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "image::draw\n"; 
   }
};

struct button : virtual public control
{
   int text_alignment;

   button(int const i) :control(i)
   {
      std::cout << "button ctor\n";
   }

   virtual ~button()
   {
      std::cout << "button dtor\n";
   }

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "button::draw\n"; 
   }
};

struct image_button : public image, public button
{
   int content_alignment;

   image_button(int const i) : image(i), button(i), control(i)
   {
      std::cout << "image_button ctor\n";
   }

   ~image_button()
   {
      std::cout << "image_button dtor\n";
   }

   virtual void draw() override
   {
      button::draw();
      image::draw();
      std::cout << "image_button::draw\n";
   }
};

int main()
{
   image_button ib{ 3 };
   ib.resize(100, 20);
}

The output of this program is as follows:

control ctor
image ctor
button ctor
image_button ctor
control::draw
button::draw
control::draw
image::draw
image_button::draw
image_button dtor
button dtor
image dtor
control dtor

A class may have both virtual and non-virtual base classes. We can change the previous example in order to demonstrate what happens in this case. Let’s consider the following modified class hierarchy:

The new hierarchy differs from the previous on as follows:

  • the image class has two base classes: non-virtual base flippable and virtual base control
  • the button class has two base classes also, both virtual: control and clickable
  • the image_button class has three base classes: non-virtual bases image and button, and virtual base class clickable

The modified implementation of these classes is shown below:

struct control
{
   int id;
   int width;
   int height;

   control(int const i) :id(i)
   {
      std::cout << "control ctor\n";
   }

   virtual ~control()
   {
      std::cout << "control dtor\n";
   }

   void resize(int const w, int const h, bool const redraw = true) 
   {
      width = w;
      height = h;
      if(redraw)
         draw();
   }

   virtual void draw() 
   { 
      std::cout << "control::draw\n"; 
   }
};

struct flippable
{
   int axis;

   flippable()
   {
      std::cout << "flippable ctor\n";
   }

   virtual ~flippable()
   {
      std::cout << "flippable dtor\n";
   }
};

struct image : public flippable, virtual public control
{
   int stretch_style;
   int stretch_direction;

   image(int const i) :control(i)
   {
      std::cout << "image ctor\n";
   }

   virtual ~image()
   {
      std::cout << "image dtor\n";
   }

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "image::draw\n"; 
   }
};

struct clickable
{
   using fn_clicked = void(*)();

   fn_clicked callback = nullptr;

   clickable()
   {
      std::cout << "clickable ctor\n";
   }

   virtual ~clickable()
   {
      std::cout << "clickable dtor\n";
   }   
};

struct button : virtual public clickable, virtual public control
{
   int text_alignment;

   button(int const i) :control(i)
   {
      std::cout << "button ctor\n";
   }

   virtual ~button()
   {
      std::cout << "button dtor\n";
   }

   virtual void draw() override
   { 
      control::draw(); 
      std::cout << "button::draw\n"; 
   }
};

struct image_button : public image, public button, virtual public clickable
{
   int content_alignment;

   image_button(int const i) : image(i), button(i), control(i)
   {
      std::cout << "image_button ctor\n";
   }

   ~image_button()
   {
      std::cout << "image_button dtor\n";
   }

   virtual void draw() override
   {
      button::draw();
      image::draw();
      std::cout << "image_button::draw\n";
   }
};

The new memory layout of the image_button class is shown in the following image:

Again, we can notice several things here:

  • the layout of the image object contains the flippable object, as this class is a non-virtual base
  • there is only one copy of the clickable object layout, as this class is a virtual base class for both button and image_button
  • the memory layout of the two virtual base classes, control and clickable, is located at the end of the image_button layout

The new output of the program is listed here:

control ctor
clickable ctor
flippable ctor
image ctor
button ctor
image_button ctor
control::draw
button::draw
control::draw
image::draw
image_button::draw
image_button dtor
button dtor
image dtor
flippable dtor
clickable dtor
control dtor

The order of the constructor calls, as seen here, as well as the destructor calls is following the several rules listed at the beginning of this section.

Alternatives

Because of this diamond problem, and perhaps because other languages do not support multiple inheritance, there is a considerable opposition to using multiple inheritance. That doesn’t necessary mean that multiple inheritance is evil or it can’t be used successfully in various scenarios. Inheritance in general should be used when it has benefits not for the purpose of reusing code. There are many cases when aggregation is a better solution than inheritance.

If you do use multiple inheritance, in general, it’s preferred that the virtual base classes are pure abstract base classes. That means only pure virtual methods and, if possible, no data members either. That is basically the equivalent of interfaces in C# or Java. Using this approach, multiple inheritance becomes equivalent to the single inheritance in these other programming languages.

An alternative to multiple inheritance is using some design patterns. A good example is the bridge design pattern that allows you to separate abstractions from the implementations.

References

You can read more about virtual inheritance here: ISO C++: Inheritance – multiple and virtual inheritance.

The memory layout images in this article were created using Struct Layout – an extension for Visual Studio.

The class diagram images in this article were created using Visual Paradigm Online – a free tool for drawing class diagrams and other UML diagrams.

8 Replies to “Virtual Inheritance in C++”

  1. great Marius. I really liked the diagrams!
    Small question – what do you think of removing the virtual keyword when using override? same question on destructor, what do you think of just having override per core guidelines ?

  2. This does not seem to be an accurate statement: “The designers of these languages didn’t considered the benefits of supporting this feature to worth the effort.” The benefits were considered — at least in case of Java or C# — but deemed not worth the (multiple) costs.

  3. Virtual base classes have another useful feature due to their placement in the initialization sequence: If a virtual base class lacks the default constructor then all of its inheriting classes must supply parameters for the virtual base class in their own constructor. This can be used to enforce all subclasses to perform some operation which the virtual base class cannot do because of missing some inheriting-class-specific information.

  4. @Vinoth, A class that has a virtual base must be able to compute the location of the virtual base from a pointer to the derived class. This is that the “virtual base table pointer” does. in VC++, objects of a class that have a virtual base have this field. It’s a pointer to a table of displacements from the vbptr address to the address of the virtual base(s). This vbptr table is shared by all the instances of the class.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.