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?
Rules:
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):
Virtual Table (vTable):
Drawbacks of Virtual Functions:
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;
}