Lesson 53 of 60 – Virtual Functions in C++
88%

Virtual Functions in C++

A virtual function is a member function in a base class that can be overridden in a derived class. It allows C++ to select the appropriate function implementation at runtime when the function is called through a base class pointer or reference.

Note: Virtual functions are one of the main features used to implement runtime polymorphism in C++.

1. What is a Virtual Function?

A virtual function is declared using the virtual keyword inside a base class.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

A derived class can provide its own implementation of sound().

2. Why Use Virtual Functions?

Virtual functions are useful when a base class pointer or reference should call the implementation belonging to the actual derived object.

They allow one common interface to work with different derived classes.

Animal* animal = &dog;

animal->sound();

If sound() is virtual, the appropriate overridden function can be selected at runtime.

3. Syntax of a Virtual Function

The basic syntax is:

class Base {

public:

    virtual void functionName() {

        // implementation
    }
};

The derived class can override the function.

class Derived : public Base {

public:

    void functionName() override {

        // new implementation
    }
};

4. Simple Virtual Function Example

#include <iostream>

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal makes a sound";
    }
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

int main() {

    Dog dog;

    dog.sound();

    return 0;
}

Here, Dog overrides the virtual sound() function.

5. Function Overriding

When a derived class provides its own implementation of a virtual function from the base class, it is called function overriding.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

class Cat : public Animal {

public:

    void sound() override {

        std::cout <<
            "Cat meows";
    }
};

6. The override Keyword

The override keyword indicates that a derived class function is intended to override a virtual function from the base class.

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

It helps the compiler detect mistakes in the function signature.

7. Base Class Pointer

A base class pointer can point to a derived class object.

Dog dog;

Animal* animal = &dog;

animal->sound();

If sound() is virtual, the derived implementation is called.

8. Runtime Polymorphism

Virtual functions are commonly used to achieve runtime polymorphism.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

int main() {

    Dog dog;

    Animal* animal = &dog;

    animal->sound();

    return 0;
}

The call is resolved according to the actual object.

9. Base Class Reference

Runtime polymorphism can also work through a base class reference.

Dog dog;

Animal& animal = dog;

animal.sound();

The virtual function mechanism selects the overridden Dog::sound() implementation.

10. Virtual Function with Multiple Classes

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

class Cat : public Animal {

public:

    void sound() override {

        std::cout <<
            "Cat meows";
    }
};

Different derived classes can provide different implementations of the same virtual function.

11. Calling Different Derived Objects

Dog dog;

Cat cat;

Animal* a1 = &dog;

Animal* a2 = &cat;

a1->sound();

a2->sound();

The same base-class interface produces different behavior for the different objects.

12. Virtual Function in a Common Function

void makeSound(
    Animal& animal
) {

    animal.sound();
}

int main() {

    Dog dog;

    Cat cat;

    makeSound(dog);

    makeSound(cat);

    return 0;
}

The function does not need separate versions for Dog and Cat. It works through the common base interface.

13. Virtual Function vs Normal Function

Feature Normal Function Virtual Function
Keyword No virtual Uses virtual
Runtime Dispatch Not provided through the virtual mechanism Supported
Overriding Can hide a base function Can provide runtime overriding behavior
Common Use Regular class behavior Runtime polymorphism

14. Virtual Function and Inheritance

Virtual functions are especially useful in inheritance hierarchies.

class Vehicle {

public:

    virtual void start() {

        std::cout <<
            "Vehicle starts";
    }

    virtual ~Vehicle() = default;
};

class Car : public Vehicle {

public:

    void start() override {

        std::cout <<
            "Car starts";
    }
};

The derived class can customize inherited behavior.

15. Pure Virtual Function

A pure virtual function is a virtual function declared with = 0.

class Shape {

public:

    virtual double area() = 0;
};

A class containing a pure virtual function is abstract.

16. Abstract Class with Virtual Function

class Shape {

public:

    virtual void draw() = 0;

    virtual ~Shape() = default;
};

class Circle : public Shape {

public:

    void draw() override {

        std::cout <<
            "Drawing Circle";
    }
};

The abstract class defines the required interface, while the derived class provides the implementation.

17. Virtual Destructor

A polymorphic base class should commonly have a virtual destructor when objects may be deleted through a base pointer.

class Animal {

public:

    virtual void sound() = 0;

    virtual ~Animal() = default;
};

This supports the correct destruction sequence when deleting derived objects through base-class pointers.

18. Virtual Destructor Example

class Base {

public:

    virtual ~Base() {

        std::cout <<
            "Base destructor";
    }
};

class Derived : public Base {

public:

    ~Derived() {

        std::cout <<
            "Derived destructor";
    }
};

int main() {

    Base* ptr = new Derived();

    delete ptr;

    return 0;
}

Because the base destructor is virtual, destruction can follow the derived-to-base sequence.

19. Virtual Function with Parameters

class Animal {

public:

    virtual void eat(
        std::string food
    ) {

        std::cout <<
            "Animal eats "
            << food;
    }

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void eat(
        std::string food
    ) override {

        std::cout <<
            "Dog eats "
            << food;
    }
};

Virtual functions can have parameters like normal member functions.

20. Virtual Function with Return Value

class Shape {

public:

    virtual double area() {

        return 0;
    }

    virtual ~Shape() = default;
};

class Rectangle : public Shape {

private:

    double length;
    double width;

public:

    Rectangle(
        double l,
        double w
    )
        : length(l),
          width(w) {
    }

    double area() override {

        return length * width;
    }
};

A virtual function can return values just like other functions.

21. Calling the Base Version

A derived class can explicitly call the base class version using the scope resolution operator.

class Animal {

public:

    virtual void sound() {

        std::cout <<
            "Animal sound";
    }
};

class Dog : public Animal {

public:

    void sound() override {

        Animal::sound();

        std::cout <<
            "Dog barks";
    }
};

Animal::sound() explicitly calls the base implementation.

22. Virtual Functions and the vtable Concept

Many C++ implementations use an internal mechanism commonly described as a virtual table (vtable) and a related pointer to support virtual dispatch.

The exact implementation is compiler-dependent and is not something the C++ language requires programmers to manage directly.

Animal* animal = &dog;

animal->sound();

The language-level idea is that the virtual call selects the appropriate overridden function for the actual object.

23. Virtual Function and Object Type

Consider a base pointer pointing to a derived object:

Dog dog;

Animal* ptr = &dog;

The pointer type is Animal*, but the actual object is a Dog.

For a virtual function call, the derived implementation can therefore be selected.

24. Practical Employee Example

#include <iostream>

class Employee {

public:

    virtual double salary() {

        return 0;
    }

    virtual ~Employee() = default;
};

class Manager : public Employee {

public:

    double salary() override {

        return 60000;
    }
};

class Developer : public Employee {

public:

    double salary() override {

        return 50000;
    }
};

int main() {

    Manager manager;

    Developer developer;

    Employee* e1 = &manager;

    Employee* e2 = &developer;

    std::cout <<
        e1->salary()
        << std::endl;

    std::cout <<
        e2->salary();

    return 0;
}

The same salary() interface can produce different results for different employee types.

25. Virtual Function and Arrays of Pointers

class Animal {

public:

    virtual void sound() = 0;

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks\n";
    }
};

class Cat : public Animal {

public:

    void sound() override {

        std::cout <<
            "Cat meows\n";
    }
};

int main() {

    Dog dog;

    Cat cat;

    Animal* animals[] = {
        &dog,
        &cat
    };

    for(Animal* animal : animals) {

        animal->sound();
    }

    return 0;
}

A collection of base pointers can work with different derived object types through the same virtual interface.

26. Common Mistakes with Virtual Functions

  • Forgetting the virtual keyword when runtime polymorphism is intended.
  • Using a different function signature when attempting to override.
  • Forgetting override in derived classes.
  • Calling through a base pointer and expecting runtime dispatch from a non-virtual function.
  • Forgetting a virtual destructor in an appropriate polymorphic base class.
  • Trying to instantiate an abstract class.
  • Confusing function overloading with function overriding.

27. Advantages of Virtual Functions

  • Runtime Polymorphism: Supports dynamic selection of overridden functions.
  • Flexibility: One interface can work with different derived objects.
  • Extensibility: New derived classes can provide new behavior.
  • Maintainability: Common code can operate through a base interface.
  • Abstraction: Abstract classes can define common operations.

28. Best Practices for Virtual Functions

  • Use virtual when runtime polymorphism is required.
  • Use override in derived classes.
  • Use a virtual destructor for suitable polymorphic base classes.
  • Keep the base interface focused and meaningful.
  • Use pure virtual functions when derived classes must provide behavior.
  • Prefer references or pointers when dynamic dispatch is required.
  • Avoid unnecessary inheritance hierarchies.
  • Use smart pointers such as std::unique_ptr when dynamic ownership is needed.

29. Real-World Uses of Virtual Functions

  • Animals: Different animals can implement sound().
  • Vehicles: Different vehicles can implement start().
  • Payments: Different payment methods can implement pay().
  • Shapes: Different shapes can implement area() or draw().
  • Employees: Different employee types can calculate salary differently.
  • Notifications: Email, SMS, and app notifications can implement send().

30. Virtual Functions – Final Summary

Concept Meaning
Virtual Function A base-class function that supports runtime dispatch when called through a suitable base pointer or reference.
Override A derived-class implementation of a virtual base-class function.
Runtime Polymorphism Calling behavior based on the actual object at runtime.
Pure Virtual Function A virtual function declared using = 0.
Abstract Class A class that contains at least one pure virtual function.
Virtual Destructor A destructor that supports appropriate polymorphic destruction through a base pointer.
class Animal {

public:

    virtual void sound() = 0;

    virtual ~Animal() = default;
};

class Dog : public Animal {

public:

    void sound() override {

        std::cout <<
            "Dog barks";
    }
};

int main() {

    Dog dog;

    Animal* animal = &dog;

    animal->sound();

    return 0;
}

This example demonstrates the core idea of virtual functions: a base-class interface can be used to call the appropriate derived implementation at runtime.

📌 Key Points

  • A virtual function is declared using the virtual keyword.
  • Virtual functions support runtime polymorphism.
  • Derived classes can override virtual functions.
  • The override keyword helps verify overriding.
  • Base class pointers and references can invoke derived implementations through virtual functions.
  • Pure virtual functions use = 0.
  • A class with a pure virtual function is abstract.
  • A suitable polymorphic base class should commonly have a virtual destructor.
  • Virtual functions are widely used with inheritance and abstraction.
  • Use virtual functions when behavior needs to vary according to the actual derived object.

🧠 Quick Quiz

Question: What is the main purpose of a virtual function in C++?