Virtual Functions, Abstract Classes & Interfaces

ভার্চুয়াল ফাংশন ও abstract class

Read: ~40 min 16 practice problems

1. The Problem with Static Dispatch

By default, member calls are bound at compile time. So basePtr->f() calls Base::f even if the actual object is a Derived. virtual fixes this:

virtual.cpp
#include <iostream>
#include <memory>
#include <vector>

class Animal {
public:
    virtual void speak() const { std::cout << "...\n"; }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() const override { std::cout << "Woof\n"; }
};

class Cat : public Animal {
public:
    void speak() const override { std::cout << "Meow\n"; }
};

int main() {
    std::vector<std::unique_ptr<Animal>> zoo;
    zoo.push_back(std::make_unique<Dog>());
    zoo.push_back(std::make_unique<Cat>());
    for (const auto& a : zoo) a->speak();
}

2. override and final

  • override — declares "I'm overriding a virtual". Compiler checks signature matches.
  • final — "no further override allowed". Block subclasses from changing it.
Always write override It catches typos and signature changes. Without it, a typo silently creates a new function instead of overriding.

3. Pure Virtual & Abstract Classes

A = 0 pure virtual function declares an interface without an implementation. Classes with any pure virtual function are abstract — you can't instantiate them.

abstract.cpp
#include <iostream>

class Shape {
public:
    virtual double area() const = 0;     // pure virtual
    virtual double perimeter() const = 0;
    virtual ~Shape() = default;
};

class Rectangle : public Shape {
    double w_, h_;
public:
    Rectangle(double w, double h) : w_(w), h_(h) {}
    double area() const override { return w_ * h_; }
    double perimeter() const override { return 2 * (w_ + h_); }
};

int main() {
    // Shape s;       // ERROR: abstract
    Rectangle r{3, 4};
    std::cout << r.area() << " " << r.perimeter() << "\n";
}

4. Virtual Destructors — Critical!

Without virtual destructor, polymorphic delete is UB
Animal* p = new Dog;
delete p; // UB if Animal::~Animal isn't virtual!
            

Rule: If a class has any virtual function, give it a virtual (or pure virtual) destructor.

5. The vtable — How It Works

Each class with virtual functions has a hidden table (vtable) of function pointers. Each instance has a hidden pointer (vptr) to the right vtable. A virtual call is one extra indirection: load vptr → look up function → call. Modern CPUs handle this fast with branch prediction.

প্রতিটি virtual function-যুক্ত class-এর জন্য compiler একটি লুকানো table (vtable) তৈরি করে, যেখানে function pointer গুলো থাকে। প্রতিটি object-এ একটি vptr থাকে যা সঠিক vtable-এর দিকে নির্দেশ করে। তাই runtime-এ সঠিক override-টি call হয়।

6. Practice Problems

  1. What does virtual do?
    ✨ Show Answer

    Marks a function for runtime dispatch. The actual function called depends on the object's dynamic type, not the static type of the pointer/reference.

  2. Why use override?
    ✨ Show Answer

    Compiler verifies that you actually are overriding a base virtual. Catches typos like void Speak() override (capital S) that would otherwise silently fail.

  3. Define an abstract base Logger with log(string).
    ✨ Show Answer
    class Logger {
    public:
        virtual void log(const std::string& msg) = 0;
        virtual ~Logger() = default;
    };
  4. Why is delete basePtr UB without virtual destructor?
    ✨ Show Answer

    Without virtual, delete uses the static type → only the base destructor runs → derived members leak.

  5. What is the runtime cost of a virtual call?
    ✨ Show Answer

    One pointer indirection (load vtable pointer) and an indirect branch. Usually ~1-2 ns; modern CPUs predict it well. Negligible unless in a tight inner loop.

  6. Can a class be abstract without any pure virtuals?
    ✨ Show Answer

    No. The presence of at least one pure virtual is what makes a class abstract.

  7. Implement Liskov substitution: a function taking const Shape& can use any subclass.
    ✨ Show Answer
    void printArea(const Shape& s) {
        std::cout << s.area() << "\n"; // dispatches dynamically
    }
  8. Difference between an abstract class and an interface (in C++)?
    ✨ Show Answer

    C++ has no separate "interface" keyword. Convention: an "interface" is an abstract class with only pure virtual methods and no data. Multiple inheritance of interfaces is fine.

  9. What's final on a method?
    ✨ Show Answer

    Marks a virtual function as not further overridable in subclasses. Lets the compiler devirtualize the call (small optimization).

  10. A pure virtual function that has a body — is that legal?
    ✨ Show Answer

    Yes. void f() const = 0; declares pure virtual; you can still define it. Subclasses can call it explicitly. Useful for default implementations.

  11. Why prefer std::unique_ptr<Base> over Base*?
    ✨ Show Answer

    Automatic delete on scope exit. Combined with virtual destructor, this is leak-proof polymorphism.

  12. Show calling base version explicitly from override.
    ✨ Show Answer
    void speak() const override {
        Animal::speak();    // call base version
        std::cout << " Woof\n";
    }
  13. Shape* ptr. Is calling a non-virtual base method polymorphic?
    ✨ Show Answer

    No. Non-virtual = static dispatch = always calls the base version, regardless of dynamic type.

  14. What is dynamic_cast<Derived*>(basePtr)?
    ✨ Show Answer

    Runtime check: if *basePtr actually is a Derived, returns Derived*; otherwise nullptr. Requires at least one virtual function in Base for RTTI to be present.

  15. Why does the slicing problem disappear with pointers/references?
    ✨ Show Answer

    Pointers/references don't copy the object. They just refer to the original full Derived object — so virtual dispatch still works.

  16. Add a describe() method to Animal that calls speak() internally.
    ✨ Show Answer
    class Animal {
    public:
        virtual void speak() const = 0;
        void describe() const {
            std::cout << "This animal says: ";
            speak(); // dispatches to derived
        }
        virtual ~Animal() = default;
    };

    This is the template method pattern: a base method orchestrates virtual hooks.

Summary

virtual enables runtime polymorphism via vtables. Always write override on derived virtuals. Pure virtual (= 0) creates abstract base classes. Always declare a virtual destructor in any class meant to be a polymorphic base. Use std::unique_ptr<Base> to manage polymorphic objects.

Next Module → Operator Overloading & Rule of Three/Five.