5.4) Virtual functions in C++
Virtual functions are a fundamental feature in C++ that enable runtime polymorphism. They allow you to define a function in a base class that can be overridden by a function with the same name and signature in its derived classes.
Virtual functions are a key aspect of object-oriented programming, as they enable you to create a common interface for objects of different classes and provide the flexibility to choose the appropriate function implementation based on the actual object type.
Here’s a more detailed explanation of virtual functions:
Table of Contents
1.Need for Virtual Functions?
Inheritance allows us to create a hierarchy of classes where a derived class inherits attributes and methods from a base class. However, when you call a method using a base class pointer or reference, it invokes the base class method, not the derived class method. This behavior does not reflect the principles of polymorphism and can lead to unexpected results.
2. Virtual Function Declaration
To declare a virtual function in a base class, you simply use the virtual keyword before the function declaration. Here’s an example:
class Base {
public:
virtual void show() {
std::cout << "Base class show() called." << std::endl;
}
};
3. Function Overriding in Derived Classes
When you want to provide specific implementations of the virtual function in derived classes, you override the function using the override keyword. Here’s an example:
class Derived : public Base {
public:
void show() override {
std::cout << "Derived class show() called." << std::endl;
}
};
4. Polymorphism and Virtual Functions
Using virtual functions, you can achieve polymorphism. Polymorphism allows you to treat objects of different classes in a unified manner through a common interface. This is particularly useful when dealing with pointers or references to base class objects.
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr;
ptr = &baseObj;
ptr->show(); // Calls the Base class show() function
ptr = &derivedObj;
ptr->show(); // Calls the Derived class show() function
return 0;
}
Output:
Base class show() called.
Derived class show() called.
In this example:
- We create an object of the
Base
class and an object of theDerived
class. - We use a pointer of type
Base*
to access both objects. - The pointer, when pointing to the
Base
object, calls theBase
class’sshow()
function. When it points to theDerived
object, it calls theDerived
class’sshow()
function. This behavior is a result of runtime polymorphism achieved through virtual functions.
Benefits of Virtual Functions
- Provides a common interface for objects of different classes.
- Allows you to work with objects of derived classes through base class pointers or references.
- Enables dynamic binding, where the appropriate function implementation is determined at runtime based on the actual object type.
- Facilitates code reusability and maintainability by promoting a modular design.
Important Considerations
- Virtual functions should be declared in the base class.
- When you override a virtual function in the derived class, use the
override
keyword to ensure that you are correctly overriding the base class’s function. - Virtual functions can be used in multiple levels of inheritance, allowing you to create complex class hierarchies with polymorphic behavior.
How virtual functions Work?
Virtual functions are implemented using a mechanism called the Virtual Function Table (VTable) or Virtual Method Table (VMT). This mechanism is used to achieve runtime polymorphism by enabling dynamic binding of virtual function calls to the appropriate function implementations in derived classes.
Here’s how virtual functions are implemented using the Virtual Function Table:
1. Virtual Function Table (VTable)
- Each class that contains at least one virtual function has an associated VTable.
- The VTable is a table of function pointers, one for each virtual function in the class. These pointers point to the actual implementations of the virtual functions in the class and its derived classes.
- The VTable is created by the compiler at compile time and is stored in memory as a static data structure for each class.
2. Object Layout
- Each object of a class that has virtual functions contains a hidden pointer called the virtual pointer or vptr as its first member.
- The vptr points to the class’s VTable, enabling dynamic dispatch of virtual function calls.
3. Function Invocation
- When you call a virtual function on a base class pointer or reference pointing to an object of a derived class, the vptr is used to find the appropriate entry in the VTable.
- The function pointer in the VTable is then used to call the correct implementation of the virtual function.
Here’s a simplified illustration of how this works:
class Shape {
public:
virtual void display() {
std::cout << "This is a shape." << std::endl;
}
};
class Circle : public Shape {
public:
void display() override {
std::cout << "This is a circle." << std::endl;
}
};
int main() {
Shape* shapePtr = new Circle();
shapePtr->display(); // Calls the correct overridden display() function
delete shapePtr;
return 0;
}
In this example:
- The
Shape
class has a virtual functiondisplay()
. - The
Circle
class overrides thedisplay()
function. - The
shapePtr
is a pointer to aShape
object but points to aCircle
object. - When
shapePtr->display()
is called, the vptr in theCircle
object is used to find the correct implementation of thedisplay()
function in the VTable.
Benefits of VTable and Virtual Functions:
- Allow dynamic binding of function calls, enabling runtime polymorphism.
- Enable objects of different types to be treated uniformly through a common interface.
- Facilitate the implementation of inheritance and object-oriented principles.
Note:
- The exact implementation details might vary based on the compiler and platform, but the concept of using VTables to achieve virtual function dispatch remains consistent.
- Virtual functions come with a slight runtime performance overhead due to the additional indirection through the VTable, compared to regular function calls. However, the benefits of runtime polymorphism usually outweigh this overhead.