Vtable and vptr in C++
In C++, "vtable" and "vptr" are words associated with the use of virtual function tables to implement functions that are virtual and polymorphisms. These ideas are crucial in comprehending how C++ accomplishes polymorphism during execution via dynamic dispatching. In this article, we will discuss Vtable and Vtr in C++ with their rules and examples.
Vtable:
Vtable is stands for Virtual Function Table. It is a data structure used by C++ compilers to construct virtual functions to promote runtime polymorphisms. Each class with a minimum of one virtual function has its own Vtable. The Vtable maintains a list of function references to the class's virtual operations, ensuring that the correct function is called at execution based on the object's underlying type.
Consider the following class hierarchy: a base class Shape and classes derived from it, Circle and Rectangle. If the Shape class has a virtual method calculateArea(), every subclass must include its instance of calculateArea() with an identical function declaration. The compiler creates a Vtable for every class and generates it with references to the relevant calculateArea() procedures.
When a derived object class is accessed via a pointer or references of the original class type, the Vtable is used to identify which function to call. This dynamic binding enables the right way to implement the function by conforming to the real object type instead of the referenced object or reference type.
Rules of virtual function table:
There are several rules of virtual function table. These rules are as follows:
- In C++, virtual functions must be members of a class. It simply means that they must be specified within a C++ class.
- In C++, virtual functions cannot be static components of the class. As a result, the virtual functions belong to one of the class's objects, not the class itself. Consider that static members are produced just once, but non-static components are created each time an extra class instance is formed.
- Continuing from the last point, virtual functions can only be made available through object references. Virtual function access through class names must be restricted.
- A Virtual function in C++ can be constructed as a friend function of another class. A friend method is a special function in C++ that has access to the private and protected member classes of the class it is "friends" with.
- Virtual functions must be declared in the base class. Functions that have been specified as virtual in the base class do not need to be tagged as virtual in derived classes. As a result, the virtualization of functions occurs at the base class level.
- The function prototype of a virtual function must be comparable to the base class and all derived classes.
Vptrs (Virtual Pointers):
C++ compilers include Virtual Pointers (Vptrs) and Vtables to support runtime polymorphism. A Vptr is a compiler-added hidden pointer variable that has been added to each objects instance that corresponds to a class that contains virtual functions. This Vptr refers to the equivalent Vtable for the class of that object.
Whenever a virtual function is invoked via an object's pointer or reference, the Vptr is dereferenced to get access to the proper Vtable, and the appropriate function that uses the reference from the Vtable is executed. This procedure ensures that the function call is routed to the correct implementations based on the object type, allowing runtime polymorphism.
Rules of Vptrs:
There are several rules of Vptrs in C++. Some of them are as follows:
- Vptrs are present in classes with atleast one virtual function and are used in these classes.
- Each class object with virtual functions receives an automatic hidden vptr addition from the compiler.
- Initialization of vptrs: During object creation, vptrs are initialized to ensure that they point to the correct vtable of the class.
- Per-Object Basis: A class containing virtual functions contains objects that each has their own vptr.
- Based on the actual type of the object, the vptr enables dynamic execution of the appropriate virtual operations.
Example 1: Virtual Function and Vtable
Let's take an example to understand the use of Vtable in C++.
#include <iostream>
class Shapes {
public:
virtual void draw() {
std::cout <<"Drawing the Shape."<< std::endl;
}
};
class Circles: public Shapes {
public:
void draw() override {
std::cout <<"Drawing the Circle."<< std::endl;
}
};
int main() {
Shapes* shapePointer = new Circles();
shapePointer->draw(); // calling the draw function
delete shapePointer;
return 0;
}
Output:
Drawing the Circle.
Explanation:
In the above example, we have the fundamental class Shape with a virtual method draw(). The draw() method is overridden by the class that derives Circles. When we construct a Circles object and access it with a Shape pointer, the Vtable and Vptr mechanisms ensure that the overridden draw() function of Circles is invoked.
Example 2: Virtual Pointer (Vptr) Visualization
Let's take an example to understand the use of Vptr in C++.
#include <iostream>
class BaseClass {
public:
virtual void func1() {
std::cout <<"The Base::function func1()"<< std::endl;
}
virtual void func2() {
std::cout <<"The Base::function func2()"<< std::endl;
}
};
class Derived: public BaseClass {
public:
void func1() override {
std::cout <<"The Derived:: function func1()"<< std::endl;
}
void func2() override {
std::cout <<"The Derived:: function func2()"<< std::endl;
}
};
int main() {
BaseClass* basePt = new Derived();
long* vptrPt = reinterpret_cast<long*>(basePt);
long* vtablePt = reinterpret_cast<long*>(*vptrPt);
typedef void (*FunctionPointer)(); // Defining the function pointer type
FunctionPointer funcPt1 = reinterpret_cast<FunctionPointer>(vtablePt[0]);
funcPt1(); // Calling the derived function funPt1()
FunctionPointer funcPt2 = reinterpret_cast<FunctionPointer>(vtablePt[1]);
funcPt2(); //Calling the derived function funPt2()
delete basePt;
return 0;
}
Output:
The Derived:: function func1()
The Derived:: function func2()
Example 3: Vtables with Multiple Inheritance
Let's take an example to understand the use of Vtable with multiple inheritance in C++.
#include <iostream>
class A1 {
public:
virtual void prints() {
std::cout <<"A1"<< std::endl;
}
};
class B1 {
public:
virtual void prints() {
std::cout <<"B1"<< std::endl;
}
};
class Child : public A1, public B1 {
public:
void prints() override {
std::cout <<"Child"<< std::endl;
}
};
int main() {
Child c1;
A1* aPt = &c1;
B1* bPt = &c1;
aPt->prints(); // Calling the C1::print()
bPt->prints(); // Calling the C1::print()
return 0;
}
Output:
Child
Child
Explanation:
In this example, class Child derives from both classes A1 and B1. When we build a component of the class Child and access it with pointers of types A1* and B1*, the compiler creates distinct Vtables for every fundamental class. The correct version of the prints() method is based on the pointer's type, highlighting the significance of Vtables in handling inheritance with multiple types and polymorphism.
Example 4: Virtual Destructor and Vptrs
#include <iostream>
class BaseClass {
public:
BaseClass() {
std::cout <<"The Base constructor"<< std::endl;
}
virtual ~BaseClass() {
std::cout <<"The Base destructor"<< std::endl;
}
};
class Derived : public BaseClass{
public:
Derived() {
std::cout <<"The Derived constructors"<< std::endl;
}
~Derived() override {
std::cout <<"The Derived destructors"<< std::endl;
}
};
int main() {
BaseClass* basePt = new Derived();
delete basePt;
return 0;
}
Output:
The Base constructor
The Derived constructors
The Derived destructors
The Base destructor