Virtual Function in C++

Why to use Virtual Function?
Virtual functions are used to achieve dynamic polymorphism which is the ability to call the appropriate Derived class function using Base class pointer or reference at runtime.

How to use Virtual Function?

  • Declare function as virtual in Base class and override that function in Derived class. (Function signature should be same in Base and Derived class)
  • Declaring a function as virtual in Base class is enough. The same function need not to be declared virtual in Derived class.
  • Virtual functions should be accessed using pointer (*) or reference (&) of Base class type to achieve runtime polymorphism.

Rules:

  • Virtual functions cannot be static and cannot be a friend function of another class.
  • A class can have virtual destructor but can’t have virtual constructor.
  • The base class destructor must be virtual if you delete a derived class object through its base class pointer.
  • You should not call virtual functions either from constructors or destructors. If it seems necessary to do so, then the design is obviously wrong.
  • It is not necessary to override the virtual function in the derived class. If the virtual function is not overridden, then the original function defined in the base class is called.

What’s the difference between how virtual and non-virtual member functions are called?
Non-virtual member functions are resolved statically. That is, the member function is selected statically (at compile-time) based on the type of the pointer (or reference) to the object.

In contrast, virtual member functions are resolved dynamically (at run-time). That is, the member function is selected dynamically (at run-time) based on the type of the object, not the type of the pointer/reference to that object. This is called “dynamic binding”. See below section for more details.

How Virtual Function is implemented or how Dynamic Polymorphism is achieved in C++?
The way compilers handle virtual functions is to add a hidden member to each class if the class or its parent class has one or more virtual functions. The hidden member is called Virtual Pointer (vPtr). This vPtr points to an array of function pointers. Such an array is termed as Virtual function table (vTable). The vTable holds the addresses of the virtual functions declared in the class or parent class.

An object of a base class, for example, contains a pointer to a table of addresses of all the virtual functions for that class. An object of a derived class contains a pointer to a separate table of addresses. If the derived class provides a new definition of a base class virtual function, its vTable holds the address of the new function. If the derived class doesn’t redefine any base class virtual function, its vTable holds the address of the base class virtual function. If the derived class defines a new function and makes it virtual, its address is added to the vTable of derived class.

When we call a virtual function at runtime, the program looks at the object’s vPtr member to get the address of corresponding vTable and then goes to the vTable to find the appropriate virtual function address to execute it. For example, if we call the first virtual function defined in the class declaration, the program uses the first function address in the vTable array and execute the function that has that address, and so on.

To summarize, virtual functions are implemented using an array/table of function pointers, called the vTable. There is one entry in the table per each virtual function in the class. vTable is created by the constructor of the class at the compile time. When a derived class is constructed, its base class is constructed first which creates the base class vTable, then derived class vTable is created. If the derived class overrides any of the base class virtual functions, those entries in the derived class vTable are overwritten by the derived class constructor. This is why you should never call virtual functions from a constructor, and that’s because the vTable entries for the object may not have been set up by the derived class constructor yet. So, you might end up calling base class implementations of those virtual functions.

Virtual Pointer (vPtr):

  • vPtr stores the address of the class’s vTable.
  • vPtr is constructed and initialized in constructor at runtime. The compiler decides at compile time if a class needs a vPtr and reserves space for it in each object, but the actual vPtr value is written into the object’s memory at runtime during object construction.
  • A vPtr is added by the compiler to objects of a class if the class has virtual functions, or if the class inherits from a class that has virtual functions.
  • vPtr is per-object (dynamic), not per-class. Each object of a class with virtual functions has its own vPtr inside its object memory layout.
  • However, all vPtrs of the same class usually point to the same vtable.
  • If a class derives from multiple bases with virtual functions, the object may contain more than one vPtr (one per polymorphic base).

Virtual Table (vTable):

  • vTable is a static table which gets constructed at compile time.
  • vTable is usually per-class (static), not per-object. Every class has its own vTable.
  • All objects of the same class share the same vtable instance.
  • vTable stores address (and sometimes metadata) of virtual functions of own class OR parent class.
  • Parent class vTable is copied to child class vTable and then if child class overrides any function, then that function address is replaced with child class function address.
  • Pure virtual functions (=0) still have an entry in the vtable, often set to nullptr or a compiler-generated stub that throws exception at runtime if called directly.

Drawbacks of Virtual Functions:

  • Each object has its size increased by the amount needed to hold the vPtr.
  • For each class, the compiler creates a table of addresses of virtual functions.
  • For each function call, there’s an extra step of going to a table to look up an address.

Example program:
Below program demonstrates almost all the use cases of virtual function.

#include<iostream>
using namespace std;

class CBase
{
public:
	void M1()
	{
		cout << "In Method CBase::M1()" << endl;
	}
	virtual void M2()
	{
		cout << "In Method CBase::M2()" << endl;
	}
	virtual void M3()
	{
		cout << "In Method CBase::M3()" << endl;
	}
	virtual void M5()
	{
		cout << "In Method CBase::M5()" << endl;
	}
};

class CDerived :public CBase
{
public:
	virtual void M1()
	{
		cout << "In Method CDerived::M1()" << endl;
	}
	virtual void M2()
	{
		cout << "In Method CDerived::M2()" << endl;
	}
	virtual void M4()
	{
		cout << "In Method CDerived::M4()" << endl;
	}
	virtual void M5(int x)
	{
		cout << "In Method CDerived::M5(int x)" << endl;
	}
};


int main()
{
	//Carefully observe the call to M5 in each case 

	//Base class pointer points to Base class object
	CBase* pBase = new CBase;
	pBase->M1(); //In Method CBase::M1()
	pBase->M2(); //In Method CBase::M2()
	pBase->M3(); //In Method CBase::M3()
	//pBase->M4(); //Compile Error as CBase::M4() is not there
	pBase->M5(); //In Method CBase::M5()
	cout << endl;

	//Base class pointer points to Derived class object
	CBase* pBase2 = new CDerived;
	pBase2->M1(); //In Method CBase::M1()
	pBase2->M2(); //In Method CDerived::M2()
	pBase2->M3(); //In Method CBase::M3()
	//pBase2->M4();	//Compile Error as CBase::M4() is not there
	pBase2->M5(); //In Method CBase::M5()
	//pBase2->M5(10); //Compile Error as call resolves to CBase::M5(int) which is not there 
	cout << endl;

	//Derived class pointer points to Derived class object
	CDerived* pDerived = new CDerived;
	pDerived->M1();	//In Method CDerived::M1()
	pDerived->M2();	//In Method CDerived::M2()
	pDerived->M3();	//In Method CBase::M3()
	pDerived->M4();	//In Method CDerived::M4()
	//pDerived->M5(); //Compile Error as call resolves to CDerived::M5() at compile time which is not there. 
			          //It can't call CBase::M5() as it's hidden by CDerived::M5(int x). It's called Method Hiding.
	pDerived->M5(10); //In Method CDerived::M5(int x)
	cout << endl;

	//Derived class pointer points to Base class object (Invalid scenario)
	//CDerived* pDerived2 = new CBase; //Compile error as Derived class pointer can't point to Base class object

	return 0;
}
Comments (3)