Virtual Inheritance

A question came up on LinkedIn in the C++ group relating to how virtual class inheritance actually works. Since LinkedIn limits the amount of space for responses, and also manages to screw up code formatting, I decided to tackle the answer here. Before continuing, I suggest you familiarize yourself with the thread in question.

Virtual base classes are different from non-virtual base classes in terms of how they are layed out in memory at runtime. You can think of a non-virtual base class as being contained within the derived class inheriting from it, much in the same way you can have one struct contain another. You can string these non-virtual base classes into a chain, and the structure remains the same. Each derived class contains its base class. Eg)

class Base1 {};
class Derived1 : public Base1 {};
class Derived2 : public Derived1 {};

// Conceptually, this would provide a layout like this:
struct B {};
struct D1 { struct B b; };
struct D2 { struct D1 d; };

When you inherit from a class virtually, it changes the rules. Instead of the class being structurally a part of the derivative, it’s instead a part of the most-derived, non-virtually inheriting class (or the most-derived class, even if it is virtually inheriting).

class Base1 {};
class Derived1 : public virtual Base1 {};
class Derived2 : public Derived1 {};

// Conceptually, this would provide a layout like this:
struct B {};
struct D1 {};
struct D2 { struct D1 d; struct B b; };

When creating an object of type D2, D1 won’t actually contain a B because D1 has declared Base1 to be a virtual class. But something has to be responsible for the initialization chain going to B. D1 can’t be relied on to do it because it doesn’t contain a B in this case, so D2 is the only thing left which can do it. This is why D2 must manually call the constructor for both D1 and B.

Taking a more concrete example:

class Base1 {
private:
	int i;

public:
	Base1(int f) : i(f) {}
};

class Derived1 : public virtual Base1 {
public:
	Derived1() : Base1(42) {}
};

class Derived2 : public Derived1 {
public:
	Derived2() : Derived1(), Base1(12) {}
};

int main()
{
	Derived1 d1;
	Derived2 d2;
	return 0;
}

The value of d1.i is 42, as you’d expect. The value of d2.i is actually 12, not 42 like you might think.

So why is this needed? Imagine the classic diamond problem:

class Base1 {
private:
	int i;

public:
	Base1(int f) : i(f) {}
};

class Derived1 : public Base1 {
public:
	Derived1() : Base1(42) {}
};

class Derived2 : public Base1 {
public:
	Derived2() : Base1(12) {}
};

class Derived3 : public Derived1, public Derived2 {
public:
	Derived3() : Derived1(), Derived2() {}
};

int main()
{
	Derived1 d1;
	Derived2 d2;
	Derived3 d3;
	return 0;
}

Conceptually, this would mean that Derived1 contains a copy of Base1, and Derived2 contains a completely separate copy of Base1. So d1.i is 42, d2.i is 12 (understandably so), but what is d3.i? It’s ambiguous — there’s no clear path to the variable, so you can’t know whether it’s Derived1’s Base1::i, or Derived2’s Base1::i.

// Conceptually
struct B { int i; };
struct D1 { struct B b; };
struct D2 { struct B b; };
struct D3 { struct D1 d1;  struct D2 d2; };

But if you have Derived1 and Derived2 inherit virtually from Base1, you get a different picture entirely. This means Derived1 and Derived2 don’t contain a copy of Base1 in the case of creating a Derived3. This means that asking for d3.i would not be ambiguous, there’s only one path to i.

// Using these declarations
class Derived1 : public virtual Base1 {
public:
	Derived1() : Base1(42) {}
};

class Derived2 : public virtual Base1 {
public:
	Derived2() : Base1(12) {}
};

// Conceptually
struct B { int i; };
struct D1 {};
struct D2 {};
struct D3 { struct D1 d1; struct D2 d2; struct B b; };

You may be wondering what d1 or d2’s layout might look like if they inherit virtually from Base1 — they’d look like this:

// Conceptually (D1 d1)
struct B { int i; };
struct D1 { struct B b; };

// Conceptually (D2 d2)
struct B { int i; };
struct D2 { struct B b; };

The take-home point from all this is that virtual base classes follow different rules from non-virtual base classes. When you inherit non-virtually, only the derivative must call the base constructor. But when you inherit virtually, the derivative must call the base constructor, and the further derivatives must do so as well!

This is probably more information than you probably ever cared to know about the subject of virtual inheritance. Hopefully that makes things more clear, but if not, keep asking.

This entry was posted in C/C++ and tagged , , . Bookmark the permalink.

4 Responses to Virtual Inheritance

  1. Beamon says:

    “The value of d1.i is 42, as you’d expect. The value of d2.i is actually 12, not 42 like you might think.”

    Did I get things right? :

    class Base1 {
    private:
    int i;

    public:
    Base1(int f) : i(f) {}
    };

    class Derived1 : public virtual Base1 {
    public:
    Derived1() : Base1(42) {}
    };

    class Derived2 : public Derived1 {
    public:
    Derived2() : Derived1(), Base1(12) {}
    };

    int main()
    {
    Derived1 d1;
    Derived2 d2;
    return 0;
    }

    so when we create d2, “Derived2() : Derived1(), Base1(12) ”
    it’s base class constructor is class which is Derived1() which in turns initializes “i” in base = 42 , then it calls constructor of Base1(12) again? but we can’t call a same constructor 2 times in c++ , right?

    can you please tell me where I’m wrong? Thanks

  2. Aaron Ballman says:

    Derived2::Derived2 is called due to creating the class instance. That constructor manually calls Base1::Base1 first, then it manually calls Derived1::Derived1. However, because the dynamic type of the object is Derived2, not Derived1, the Derived1 constructor does not call the Base1 constructor. So if you were to put printfs into the constructors, you would see it print out: Base1, Derived1, Derived2.

    When creating the Derived1 object, Derived1::Derived1 is called due to creating the class instance. That constructor will then call Base1::Base1 because the dynamic type of the object (this) is Derived1.

    Does this clear things up a bit?

  3. Beamon says:

    yes , that clears up thing , Thanks :)

    P.S: You do cool explaining :)

  4. Aaron Ballman says:

    Thanks! Glad I could help. :-)

Leave a Reply

Your email address will not be published.