Virtual Functions and Polymorphism

Virtual functions and polymorphism are core concepts in Object-Oriented Programming that allow for more flexible and dynamic code. These concepts are essential for implementing runtime method binding, which enables a program to determine which method to call at runtime rather than at compile time.

What are Virtual Functions?

Virtual functions are member functions that are declared in a base class and redefined (overridden) in derived classes. They are declared using the virtual keyword in the base class.

class Base {
public:
    virtual void display() {
        cout << "Display Base class" << endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        cout << "Display Derived class" << endl;
    }
};

Key Characteristics:

  1. Late Binding: The function to be executed is determined at runtime, not at compile time.
  2. Override Mechanism: They provide a way for derived classes to override functionality of base class.
  3. Base Class Pointer: They enable the use of base class pointers to call functions of derived class objects.

Why Do We Need Virtual Functions?

Virtual functions solve the problem of function overriding when using base class pointers or references to derived class objects. Without virtual functions, the function call is resolved at compile time (early binding), which can lead to unexpected behavior.

Example Without Virtual Functions:

class Base {
public:
    void display() {
        cout << "Display Base class" << endl;
    }
};

class Derived : public Base {
public:
    void display() {
        cout << "Display Derived class" << endl;
    }
};

int main() {
    Derived d;
    Base* basePtr = &d;
    
    // This will call Base::display(), not Derived::display()
    basePtr->display();  // Output: "Display Base class"
    
    return 0;
}

Example With Virtual Functions:

class Base {
public:
    virtual void display() {
        cout << "Display Base class" << endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        cout << "Display Derived class" << endl;
    }
};

int main() {
    Derived d;
    Base* basePtr = &d;
    
    // This will call Derived::display()
    basePtr->display();  // Output: "Display Derived class"
    
    return 0;
}

Virtual Function Table (vtable)

For each class that contains virtual functions, the compiler creates a virtual function table (vtable). Each object of such a class contains a hidden pointer to this vtable. This mechanism is what enables dynamic binding.

Virtual Function Table Illustration

Virtual functions might impact performance slightly due to the additional indirection through the vtable, but the flexibility they provide often outweighs this cost.

What is Polymorphism?

Polymorphism is one of the four fundamental principles of object-oriented programming. The word “polymorphism” comes from Greek words meaning “many forms.” In C++, polymorphism allows objects of different classes to be treated as objects of a common base class.

Polymorphism enables a single interface to represent different underlying forms (data types). It allows us to perform a single action in different ways depending on the object that executes the action.

Types of Polymorphism in C++

C++ supports two types of polymorphism:

  1. Compile-time Polymorphism (Static Binding or Early Binding)

    • Function Overloading
    • Operator Overloading
    • Templates
  2. Runtime Polymorphism (Dynamic Binding or Late Binding)

    • Virtual Functions
    • Function Overriding

Compile-time Polymorphism

Compile-time polymorphism is resolved during the compilation of the program.

Function Overloading

Function overloading allows multiple functions with the same name but different parameters (number, type, or order of parameters).

#include <iostream>
using namespace std;

// Function with integer parameter
void display(int num) {
    cout << "Integer number: " << num << endl;
}

// Function with double parameter
void display(double num) {
    cout << "Double number: " << num << endl;
}

// Function with two parameters
void display(int num1, int num2) {
    cout << "Two integers: " << num1 << " and " << num2 << endl;
}

int main() {
    display(5);        // Calls the first function
    display(5.5);      // Calls the second function
    display(5, 10);    // Calls the third function
    
    return 0;
}

Output:

Integer number: 5
Double number: 5.5
Two integers: 5 and 10

Operator Overloading

Operator overloading allows operators to work with user-defined data types.

// Complex number class with overloaded + operator
class Complex {
private:
    double real;
    double imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // Overloading + operator
    Complex operator + (const Complex& obj) {
        Complex temp;
        temp.real = real + obj.real;
        temp.imag = imag + obj.imag;
        return temp;
    }
    
    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3, 4), c2(5, 6);
    Complex c3 = c1 + c2;  // Using overloaded + operator
    c3.display();          // Output: 8 + 10i
    
    return 0;
}

Runtime Polymorphism

Runtime polymorphism is resolved during the execution of the program.

Virtual Functions

A virtual function is a member function in the base class that is declared using the virtual keyword. When a derived class overrides this function, the function to be called is determined at runtime based on the type of the object being referred to.

#include <iostream>
using namespace std;

class Base {
public:
    virtual void display() {
        cout << "Display method from Base class" << endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        cout << "Display method from Derived class" << endl;
    }
};

int main() {
    Base* basePtr;     // Base class pointer
    Derived derivedObj;  // Derived class object
    
    basePtr = &derivedObj;  // Base pointer points to derived object
    
    // Will call the Derived class display() method
    basePtr->display();  // Output: Display method from Derived class
    
    return 0;
}

What is a Virtual Function?

A virtual function is a member function in a base class that is declared using the virtual keyword. It can be overridden by derived classes, allowing for runtime polymorphism when accessed through a base class pointer or reference.

Key characteristics of virtual functions:

  1. They are declared in a base class with the virtual keyword
  2. They are intended to be overridden in derived classes
  3. They enable runtime polymorphism
  4. They allow a base class pointer to call the appropriate derived class function

Why Use Virtual Functions?

Virtual functions are used to achieve runtime polymorphism. They allow us to:

  1. Create a common interface in a base class
  2. Provide specific implementations in derived classes
  3. Call the appropriate function based on the actual object type, not the pointer/reference type
  4. Implement the “one interface, multiple implementations” principle

Syntax of Virtual Functions

class Base {
public:
    virtual return_type function_name(parameters) {
        // Base class implementation
    }
};

class Derived : public Base {
public:
    // Override the base class function
    return_type function_name(parameters) override {
        // Derived class implementation
    }
};

Virtual Function Example

#include <iostream>
using namespace std;

class Shape {
public:
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
    
    virtual double area() {
        cout << "Calculating area of a generic shape" << endl;
        return 0;
    }
};

class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : public Shape {
private:
    double width;
    double height;
    
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    void draw() override {
        cout << "Drawing a rectangle" << endl;
    }
    
    double area() override {
        return width * height;
    }
};

int main() {
    Shape* shape;
    
    Circle circle(5);
    Rectangle rect(4, 6);
    
    // Store address of circle
    shape = &circle;
    
    // Call circle draw function
    shape->draw();
    cout << "Area: " << shape->area() << endl;
    
    // Store address of rectangle
    shape = &rect;
    
    // Call rectangle draw function
    shape->draw();
    cout << "Area: " << shape->area() << endl;
    
    return 0;
}

Output:

Drawing a circle
Area: 78.5398
Drawing a rectangle
Area: 24

How Virtual Functions Work in C++

When a class contains a virtual function, the compiler creates a virtual table (vtable) for that class. The vtable is a lookup table of function pointers used to resolve virtual function calls at runtime.

  1. Each class with virtual functions has its own vtable
  2. Each object of such a class contains a hidden pointer to its class’s vtable (vptr)
  3. When a virtual function is called through a base class pointer/reference, the program uses the vptr to find the correct function in the vtable

Virtual Destructors

If a class has virtual functions, it should also have a virtual destructor. This ensures that when an object is deleted through a base class pointer, the proper destructor sequence is called.

class Base {
public:
    virtual void display() {
        cout << "Base display" << endl;
    }
    
    // Virtual destructor
    virtual ~Base() {
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        cout << "Derived display" << endl;
    }
    
    ~Derived() override {
        cout << "Derived destructor" << endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->display();
    delete ptr;  // Calls both Derived and Base destructors
    
    return 0;
}

Output:

Derived display
Derived destructor
Base destructor

The override Keyword

C++11 introduced the override keyword to explicitly indicate that a function in a derived class is meant to override a virtual function in the base class. This helps catch errors at compile-time if the function signature doesn’t match any virtual function in the base class.

class Base {
public:
    virtual void func(int x) {
        cout << "Base::func(int)" << endl;
    }
};

class Derived : public Base {
public:
    // Correctly overrides Base::func(int)
    void func(int x) override {
        cout << "Derived::func(int)" << endl;
    }
    
    // Compiler error: no function to override
    void func(double x) override {
        cout << "Derived::func(double)" << endl;
    }
};

The final Keyword

C++11 also introduced the final keyword, which can be used to:

  1. Prevent a virtual function from being overridden in further derived classes
  2. Prevent a class from being inherited
class Base {
public:
    virtual void func() {
        cout << "Base::func()" << endl;
    }
};

class Derived : public Base {
public:
    // This function cannot be overridden by any further derived classes
    void func() override final {
        cout << "Derived::func()" << endl;
    }
};

// This class cannot be inherited from
class FinalClass final {
    // Class members
};

// Error: cannot derive from 'final' class
class AnotherClass : public FinalClass {
    // Class members
};

Pure Virtual Functions and Abstract Classes

A pure virtual function is a virtual function with no implementation in the base class. It is declared by adding = 0 to the function declaration:

virtual return_type function_name(parameters) = 0;

A class with at least one pure virtual function becomes an abstract class. Abstract classes:

  1. Cannot be instantiated directly
  2. Are designed to be inherited from
  3. Force derived classes to implement the pure virtual functions
  4. Can have normal member functions and data
#include <iostream>
using namespace std;

// Abstract class
class Shape {
public:
    // Pure virtual functions
    virtual void draw() = 0;
    virtual double area() = 0;
    
    // Regular function
    void showInfo() {
        cout << "This is a shape with area " << area() << endl;
    }
    
    // Virtual destructor
    virtual ~Shape() {}
};

// Concrete class
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
    
    double area() override {
        return 3.14159 * radius * radius;
    }
};

int main() {
    // Shape shape;  // Error: Cannot create an instance of an abstract class
    
    Circle circle(5);
    circle.draw();
    circle.showInfo();
    
    Shape* shapePtr = &circle;
    shapePtr->draw();
    
    return 0;
}

Output:

Drawing a circle
This is a shape with area 78.5398
Drawing a circle

Difference Between Virtual Functions and Pure Virtual Functions

Virtual FunctionsPure Virtual Functions
Declared with virtual keywordDeclared with virtual keyword and = 0
Have an implementation in the base classNo implementation in the base class
Can be called directlyCannot be called directly from the base class
Base class is not necessarily abstractMake the base class abstract
Derived classes can but don’t have to overrideDerived classes must implement them to be concrete

Runtime Polymorphism Through Virtual Functions

Runtime polymorphism is one of the most powerful features of C++ and is implemented through virtual functions. Here’s a classic example using different types of animals:

#include <iostream>
#include <vector>
using namespace std;

class Animal {
public:
    virtual void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
    
    virtual ~Animal() {}
};

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

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

class Duck : public Animal {
public:
    void makeSound() override {
        cout << "Duck quacks: Quack quack!" << endl;
    }
};

void animalSound(Animal& animal) {
    // This will call the appropriate derived class method
    animal.makeSound();
}

int main() {
    Dog dog;
    Cat cat;
    Duck duck;
    
    // Using function that takes base class reference
    animalSound(dog);
    animalSound(cat);
    animalSound(duck);
    
    // Using base class pointers
    Animal* animals[3];
    animals[0] = &dog;
    animals[1] = &cat;
    animals[2] = &duck;
    
    cout << "\nUsing array of pointers:" << endl;
    for (int i = 0; i < 3; i++) {
        animals[i]->makeSound();
    }
    
    // Using vector and pointers for dynamic collection
    vector<Animal*> animalVec;
    animalVec.push_back(&dog);
    animalVec.push_back(&cat);
    animalVec.push_back(&duck);
    
    cout << "\nUsing vector of pointers:" << endl;
    for (const auto& animal : animalVec) {
        animal->makeSound();
    }
    
    return 0;
}

Output:

Dog barks: Woof woof!
Cat meows: Meow meow!
Duck quacks: Quack quack!

Using array of pointers:
Dog barks: Woof woof!
Cat meows: Meow meow!
Duck quacks: Quack quack!

Using vector of pointers:
Dog barks: Woof woof!
Cat meows: Meow meow!
Duck quacks: Quack quack!

Common Pitfalls with Virtual Functions

  1. Not using virtual destructors: When deleting a derived class object through a base class pointer, a non-virtual destructor will only call the base class destructor.

  2. Calling virtual functions from constructors or destructors: The virtual function called will be the one defined in the class whose constructor or destructor is being executed, not any derived class.

  3. Not making base class destructors virtual: If you use polymorphism, always make the base class destructor virtual.

  4. Hiding base class virtual functions: If a derived class defines a function with the same name but different parameters, it hides the base class function rather than overriding it.

class Base {
public:
    virtual void func(int x) {
        cout << "Base::func(int)" << endl;
    }
};

class Derived : public Base {
public:
    // This hides Base::func(int), not overrides it
    void func(double x) {
        cout << "Derived::func(double)" << endl;
    }
};

int main() {
    Derived d;
    d.func(10.5);  // Calls Derived::func(double)
    d.func(10);    // Also calls Derived::func(double) - int converted to double
    // d.Base::func(10);  // Can call base version explicitly
    
    return 0;
}

Best Practices for Using Virtual Functions

  1. Use virtual destructors in base classes that have virtual functions.

  2. Use the override keyword to make it clear when you’re overriding a virtual function.

  3. Don’t call virtual functions from constructors or destructors.

  4. Keep the public interface simple and push implementation details to protected/private sections.

  5. Consider making base classes abstract if they’re only meant to be interfaces.

  6. Use virtual inheritance when dealing with the “diamond problem” in multiple inheritance.

  7. Consider the performance implications: Virtual functions have a slight overhead due to the vtable lookup.

Summary

Virtual functions and polymorphism are fundamental concepts in object-oriented programming in C++. They allow for:

  1. Creating flexible and extensible code through common interfaces
  2. Implementing the “program to an interface, not an implementation” principle
  3. Achieving runtime binding to call the appropriate function based on the actual object type
  4. Building robust class hierarchies with code reuse

By understanding and properly implementing virtual functions, you can create more maintainable and extensible C++ programs that fully utilize the power of object-oriented programming.