Virtual Functions and Pure virtual Functions

This chapter delves deep into the world of virtual functions and pure virtual functions in C++, providing a comprehensive understanding from the ground up. We'll explore the concepts, their functionalities, and how they enable polymorphism, a powerful feature in object-oriented programming.

What are Virtual Functions?

Imagine a family with a shared trait like “greet.” Each member might greet differently (waving, bowing, etc.). In C++, virtual functions work similarly. They are member functions declared within a base class that can be redefined (overridden) in derived classes. This allows derived classes to provide their own implementation of the function, specific to their needs.

Why Use Virtual Functions?

Virtual functions are the foundation for polymorphism, which means “many forms.” When you have a base class pointer or reference that can point to objects of different derived classes, virtual functions enable you to call the appropriate function based on the object’s actual type at runtime (dynamic binding).

Declaring a Virtual Function

To declare a virtual function in a base class, use the virtual keyword before the function’s return type:

				
					class Shape {
public:
  virtual void draw() {
    std::cout << "Drawing a generic shape..." << std::endl;
  }
};

				
			

Overriding a Virtual Function:

Derived classes can override the virtual function from the base class by redeclaring it with the same name and parameter list. No virtual keyword is needed in the derived class:

				
					class Circle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing a circle..." << std::endl;
  }
};

				
			

Polymorphism with Virtual Functions

The magic happens when you use a base class pointer or reference to hold objects of different derived classes. Here’s an example:

				
					void printShape(Shape* shape) {
  shape->draw(); // Calls the overridden version based on the object's type
}

int main() {
  Circle circle;
  Shape* shapePtr = &circle; // Base class pointer to a derived class object
  printShape(shapePtr); // Output: Drawing a circle...
}

				
			

In this example, even though shapePtr is a base class pointer, calling draw() through it invokes the overridden version from the Circle class (dynamic binding).

What are Pure Virtual Functions?

Pure virtual functions are a special type of virtual function declared with the virtual keyword and followed by = 0. They act as placeholders in the base class, forcing derived classes to provide their implementation. A base class containing a pure virtual function is called an abstract class.

Why Use Pure Virtual Functions?

Pure virtual functions enforce a common interface (behavior) for derived classes. They ensure that all derived classes implement the functionality defined by the pure virtual function. This is useful for creating base classes that represent abstract concepts.

Declaring a Pure Virtual Function

				
					class Animal {
public:
  virtual void makeSound() = 0; // Pure virtual function
};

				
			

This declares a pure virtual function makeSound() in the Animal class. Derived classes like Dog and Cat must override it to provide their specific sounds.

Abstract Classes

A class with at least one pure virtual function is considered an abstract class. You cannot create objects directly from an abstract class because it doesn’t provide a complete implementation.

				
					class Animal {
public:
  virtual void makeSound() = 0; // Pure virtual function
};

				
			

Example with Abstract Class and Pure Virtual Function

				
					class Animal {
public:
  virtual void makeSound() = 0;
};

class Dog : public Animal {
public:
  void makeSound() override {
    std::cout << "Woof!" << std::endl;
  }
};

class Cat : public Animal {
public:
  void makeSound() override {
    std::cout << "Meow!" << std::endl;
  }
};

int main() {
  // Animal animal; // Error: Cannot create object of abstract class

  Dog dog;
  Cat cat;

  dog.makeSound(); // Output: Woof!
  cat.makeSound(); // Output: Meow!
}

				
			

Benefits of Pure Virtual Functions

  • Enforce consistent behavior across derived classes.
  • Create abstract base classes representing concepts.
  • Enable polymorphism with a well-defined interface

Deep Dive into Virtual Function Mechanics

Virtual Table (vtable)

Behind the scenes, virtual functions rely on a concept called the virtual table (vtable). It’s a table maintained within the class hierarchy that stores pointers to the member function implementations for each virtual function. When a virtual function is called through a base class pointer or reference, the vtable is used to determine the correct function to execute based on the object’s actual type.

Dynamic Binding

The process of resolving which virtual function to call at runtime using the vtable is called dynamic binding. This allows for polymorphism, where the same function call on a base class pointer can invoke different implementations depending on the derived class object it points to.

Early vs. Late Binding

  • Early Binding (Static Binding): When a function is called through a non-virtual function or a member function on the same object it’s declared in, the compiler determines the function to call at compile time (static binding). No vtable lookup is involved.
  • Late Binding (Dynamic Binding): When a virtual function is called through a base class pointer or reference, the vtable is consulted at runtime to find the appropriate implementation for the object’s type (dynamic binding).

Code Example with vtable and Dynamic Binding

				
					#include <iostream>

class Shape {
public:
  virtual void draw() {
    std::cout << "Drawing a generic shape..." << std::endl;
  }
};

class Circle : public Shape {
public:
  void draw() override {
    std::cout << "Drawing a circle..." << std::endl;
  }
};

int main() {
  Shape* shapePtr = new Circle; // Base class pointer to a derived class object

  // vtable lookup happens here at runtime to call Circle's draw()
  shapePtr->draw(); // Output: Drawing a circle...

  delete shapePtr;
}

				
			

Key Points

  • The vtable is invisible to the programmer but manages virtual function calls efficiently.
  • Dynamic binding ensures the correct function implementation is executed based on the object’s type at runtime.

Advanced Concepts with Virtual Functions

Virtual Destructors

When dealing with inheritance and memory management, virtual destructors become crucial. A virtual destructor is declared in the base class with the virtual ~ syntax. Its purpose is to ensure proper destruction of objects in a derived class hierarchy when you delete a base class pointer pointing to a derived class object.

RTTI (Run-Time Type Information)

RTTI is a mechanism that allows you to determine an object’s type at runtime. C++ provides the typeid operator for this purpose. However, using RTTI can be considered an anti-pattern in some cases, as it can introduce tight coupling and reduce code readability. Prefer polymorphism through virtual functions whenever possible.

Abstract Base Classes and Interfaces

Abstract base classes with pure virtual functions act as interfaces, defining a set of behaviors that derived classes must implement. This enforces a contract and promotes code reusability.

Virtual functions and pure virtual functions are powerful tools in C++ for achieving polymorphism and creating flexible object-oriented designs. By understanding their mechanisms, benefits, and limitations, you can leverage them effectively to write robust and maintainable code.Happy coding !❤️

Table of Contents