← Back to C++ Course | Chapter 9: Inheritance | Lesson 8 of 8

C++ Virtual Base Class

Why Virtual Base Classes?

When a diamond-shaped inheritance pattern exists, a naive derived class ends up with two separate copies of the shared grandparent class — one through each parent. Marking that grandparent as a virtual base class ensures only a single, shared copy is ever created, no matter how many paths lead to it.

Example: Why Virtual Base Classes?

cpp
#include <iostream>

class Grandparent {
public:
	int value = 1;
};

class ParentA : virtual public Grandparent {};
class ParentB : virtual public Grandparent {};
class Grandchild : public ParentA, public ParentB {};

int main() {
	Grandchild g;
	std::cout << g.value << std::endl;
	return 0;
}

Declaring Virtual Base Classes

You declare a virtual base class by adding the virtual keyword during the inheritance declaration in the intermediate classes; the order in which you write virtual and the access specifier like public doesn't matter to the compiler.

Example: Declaring Virtual Base Classes

cpp
#include <iostream>

class Base {
public:
	int value = 10;
};

class DerivedA : virtual public Base {};
class DerivedB : virtual public Base {};

int main() {
	DerivedA a;
	std::cout << a.value << std::endl;
	return 0;
}

Constructor Rules with Virtual Base

With a virtual base class, responsibility for calling the shared grandparent's constructor shifts all the way down to the most-derived (grandchild) class — C++ deliberately ignores any attempt by the intermediate parent classes to call that constructor themselves.

Example: Constructor Rules with Virtual Base

cpp
#include <iostream>

class Base {
public:
	Base() { std::cout << "Base constructed" << std::endl; }
};

class ParentA : virtual public Base {};
class ParentB : virtual public Base {};

class Grandchild : public ParentA, public ParentB {
public:
	Grandchild() : Base() { std::cout << "Grandchild constructed" << std::endl; }
};

int main() {
	Grandchild g;
	return 0;
}

Accessing Grandparent Members

Because only one instance of the virtual base class exists no matter how many derivation paths lead to it, modifying its member variables from any subclass changes that single shared copy, visible consistently through every path in the hierarchy.

Example: Accessing Grandparent Members

cpp
#include <iostream>

class Base {
public:
	int shared = 0;
};

class ParentA : virtual public Base {};
class ParentB : virtual public Base {};
class Grandchild : public ParentA, public ParentB {};

int main() {
	Grandchild g;
	g.shared = 99;
	std::cout << g.shared << std::endl;
	return 0;
}

Destructor Sequence

In virtual inheritance, the single shared virtual base object is destroyed last of all, after every subclass along every path has already finished running its own destructor — the reverse of the special-cased construction order.

Example: Destructor Sequence

cpp
#include <iostream>

class Base {
public:
	~Base() { std::cout << "Base destroyed last" << std::endl; }
};

class ParentA : virtual public Base {
public:
	~ParentA() { std::cout << "ParentA destroyed" << std::endl; }
};

class Grandchild : public ParentA {
public:
	~Grandchild() { std::cout << "Grandchild destroyed first" << std::endl; }
};

int main() {
	Grandchild g;
	return 0;
}

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.