Virtual Function in C++

Virtual Function in C++

Table of Contents:

In object-oriented programming, one of the principles of polymorphism is that objects belonging to different classes may be treated as objects of a common base class. In C++, polymorphism is mainly achieved through the use of virtual functions, just as it allows dynamic method resolution to occur with runtime binding.

In this article, we will discuss the need for virtual functions c++, their uses and rules, working and limitations, and a comparison of their behavior.

What is a Virtual Function in C++?

A virtual function is a member function in C++ that is declared in the base class with the keyword virtual and is meant to be overridden in a derived class. It permits late binding that the function call is resolved at runtime based on the actual type of the object, and not at compile time.

Example:

Cpp
#include
using namespace std;
class Base {
public:
virtual void show() { // Virtual function
cout << "Base class function" << endl;
}
};
class Derived : public Base {
public:
void show() override { // Overriding the virtual function
cout << "Derived class function" <show(); // Calls Derived class function (dynamic binding)
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The code shows virtual functions in C++, where a base class function is marked as virtual to enable binding. When a base class pointer points to a derived class object, the overridden function in the derived class is invoked at runtime instead of the base class version.

Rules for Virtual Functions in C++

  • It must be declared using the virtual keyword in the base class.
  • The virtual function can be overridden by a derived class using the same signature only.
  • Virtual functions are resolved at runtime using the vtable and vptr mechanism.
  • Virtual functions belong to classes, so they cannot be static.
  • Constructors cannot be virtual, but destructors should be virtual to ensure proper cleanup.
  • If a class has at least one virtual function, the compiler creates a vtable.
  • Declaring a virtual function as equal to 0 makes it pure virtual, and requires derived classes to implement it.

Get 100% Hike!

Master Most in Demand Skills Now!

Reasons for Using the Virtual Functions in C++

1. Enable Runtime Polymorphism: It allows the function calls to be resolved at the time of runtime which is based on the actual object.

Example:

Cpp
#include
using namespace std;
class Base {
public:
virtual void show() { // Virtual function
cout << "Base class function" << endl;
}
};
class Derived : public Base {
public:
void show() override { // Overriding the virtual function
cout << "Derived class function" <show(); // Calls Derived class function (runtime polymorphism)
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The code shows runtime polymorphism in virtual functions in C++, where a base class function is marked as virtual to enablebinding.

2. Achieve Binding: It makes sure that the correct overridden function is used when using the base class pointers or the references.

Example:

Cpp
#include
using namespace std;
class Base {
public:
virtual void show() { // Virtual function
cout << " " << endl;
}
};
class Derived : public Base {
public:
void show() override { // Overriding the virtual function
cout << "Let's learn about Virtual Function" <show(); // Calls Derived's show() due to dynamic binding
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The virtual function show()in this code ensures dynamic binding in the base class, allowing the correct function to be called.

3. Support Code Reusability: It helps us in writing extendable and maintainable object-oriented programs.

Example:

Cpp
#include
using namespace std;
class Animal {
public:
virtual void makeSound() {
cout << "Animal makes a sound" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
cout << "Dog barks" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "Cat meows" <makeSound(); // Calls Dog's makeSound
a2->makeSound(); // Calls Cat's makeSound
delete a1;
delete a2;
return 0;
}
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

This code shows code reusability by using a base class (Animal) with a virtual function (makeSound).

4. Avoid Manual Type Checking: It eliminates the need for the statements that are used to determine object types.

Example:

Cpp
#include
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing Shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
class Square : public Shape {
public:
void draw() override {
cout << "Drawing Square" <draw(); // Calls the correct overridden function
}
int main() {
Circle c;
Square s;
renderShape(&c); // No need for manual type checking
renderShape(&s);
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The virtual function draw() ensures the correct function is called at runtime, avoiding the need for manual type checking, and the renderShape() function works with any derived class without identifying the object type.

5. Ensure Proper Destructor Call: It helps to prevent memory leaks by ensuring that the derived class destructor is correctly called.

Example:

Cpp
#include
using namespace std;
class Base {
public:
Base() { cout << "Base Constructor" << endl; }
virtual ~Base() { cout << "Base Destructor" << endl; } // Virtual destructor
};
class Derived : public Base {
public:
Derived() { cout << "Derived Constructor" << endl; }
~Derived() { cout << "Derived Destructor" << endl; }
};
int main() {
Base* obj = new Derived();
delete obj; // Calls Derived destructor first, then Base destructor
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The virtual function ensures that when deleting a base class pointer, the derived class destructor executes first. Also, without a virtual destructor, only the base class destructor would run, causing incomplete cleanup.

6. Facilitate Interface Design: It allows the defining base class interface so that the derived class can be implemented without making changes in the existing codes.

Example:

Cpp
#include
using namespace std;
// Abstract base class (Interface)
class PaymentMethod {
public:
virtual void processPayment(double amount) = 0; // Pure virtual function
virtual ~PaymentMethod() {} // Virtual destructor
};
// Derived class for Credit Card Payment
class CreditCard : public PaymentMethod {
public:
void processPayment(double amount) override {
cout << "Processing Credit Card payment of $" << amount << endl;
}
};
// Derived class for PayPal Payment
class PayPal : public PaymentMethod {
public:
void processPayment(double amount) override {
cout << "Processing PayPal payment of $" << amount <processPayment(amount); // Calls the appropriate method dynamically
}
int main() {
CreditCard cc;
PayPal pp;
makePayment(&cc, 100.50); // Uses CreditCard's processPayment
makePayment(&pp, 75.25); // Uses PayPal's processPayment
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

This code shows how the interface design is facilitated by the virtual functions.

Working of Virtual Functions in C++

The virtual functions in C++ enable binding using a method called the Virtual Table (vtable) and Virtual Pointer (vptr).

1. Virtual Table (vtable):

  • Each class with virtual functions has a vtable, which is a table storing function pointers to the virtual functions of that class.
  • If a derived class overrides a virtual function, its vtable contains a pointer to the overridden function.

2. Virtual Pointer (vptr):

  • Every object of a class with virtual functions contains a hidden pointer (vptr) pointing to the class’s vtable.
  • When a virtual function is called through a base class pointer, the vptr is used to fetch the correct function from the vtable at runtime.

Example:

Cpp
#include
using namespace std;
class Base {
public:
virtual void show() { // Virtual function
cout << "Welcome to Intellipaat" << endl;
}
};
class Derived : public Base {
public:
void show() override { // Overriding virtual function
cout << "Hello Intellipaat" <show(); // Calls Derived's show() due to vtable mechanism
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The above code shows how the vtable and vptr are used for binding in C++.

Limitations of Virtual Functions in C++

  • The extra indirection via vtable makes the virtual function calls slower.
  • The use of virtual functions increases memory usage, as it requires vtable and vptr.
  • The compiler cannot inline virtual functions, thus reducing optimization.
  • Managing vtable and dynamic dispatch increases program complexity.
  • The manipulation in vtable can lead to security issues.
  • Also managing multiple vtables can be complex and may cause errors.

Compile time vs Runtime Behavior of Virtual Functions in C++

1. Compile-Time Behavior:

  • The constructor creates a vtable for classes with virtual methods.
  • Every class with virtual methods has its own vptr, set up in its constructor.
  • Non-virtual methods are statically bound at compile time.
  • The compiler checks function declaration, overriding rules, and access control.

2. Runtime Behavior:

  • Dynamically-vtable-resolved virtual function calls at runtime.
  • The vptr checks which function was implemented.
  • Calls are executed depending on the actual type of the referenced object at runtime.
  • Virtual function calls are lengths just a bit slower than non-virtual ones as they have to deal with vtable.

Example:

Cpp
#include
using namespace std;
class Base {
public:
virtual void show() { cout << "Base class show()" << endl; } // Virtual function
void display() { cout << "Base class display()" << endl; } // Non-virtual function
};
class Derived : public Base {
public:
void show() override { cout << "Derived class show()" << endl; } // Overriding virtual function
void display() { cout << "Derived class display()" <show(); // Runtime behavior (Late binding) → Calls Derived's show()
ptr->display(); // Compile-time behavior (Early binding) → Calls Base's display()
return 0;
}
 
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Output:

The code shows early (compile-time) and late (runtime) binding in C++ using virtual functions. The show() function is virtual, so it is resolved at runtime (calls Derived::show()), while display() is non-virtual, so it follows compile-time binding (calls Base::display()).

Feature Compile-time Behavior Runtime Behavior
Function BindingStatic(Early) BindingDynamic(Late) Binding
Function ResolutionResolved by compilerResolved via vtable at runtime
SpeedFasterSlightly slower
Function call techniqueDirect function callUses vtable lookup
Works withNormal functionsVirtual functions

Conclusion

Virtual functions enable runtime polymorphism in C++, realized by the mechanism of vtable and vptr in dynamic function resolution. The advantages of these are the reusability, modularity, and extensibility of the code, whereas their disadvantages involve performance overhead and a certain added complexity. Knowledge of compile-time vs. runtime flow allows the developer to optimize their use, in inheritance handling, and good memory management.

FAQs

1. Can a constructor be virtual in C++?

No, a constructor cannot be virtual because one cannot set up a vtable before a constructor is initialized for an object.

2. Should destructors be virtual in base classes?

Yes, destructors should be virtual in base classes to ensure proper cleanup.

3. How do virtual functions work internally?

A vtable (virtual table) and vptr (virtual pointer) are used in C++ to manage virtual function calls.

4. What is the difference between compile-time and runtime behavior of virtual functions?

In compile time, the constructor creates a vtable for classes with virtual methods. While in runtime, dynamically-vtable is resolved using the virtual functions.

About the Author

Senior Consultant Analytics & Data Science

Sahil Mattoo, a Senior Software Engineer at Eli Lilly and Company, is an accomplished professional with 14 years of experience in languages such as Java, Python, and JavaScript. Sahil has a strong foundation in system architecture, database management, and API integration.