Virtual Functions, Abstract Classes & Interfaces
ভার্চুয়াল ফাংশন ও abstract class
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:
#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.
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.
#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!
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.
6. Practice Problems
- What does
virtualdo?✨ 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.
- 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. - Define an abstract base
Loggerwithlog(string).✨ Show Answer
class Logger { public: virtual void log(const std::string& msg) = 0; virtual ~Logger() = default; }; - Why is
delete basePtrUB without virtual destructor?✨ Show Answer
Without virtual,
deleteuses the static type → only the base destructor runs → derived members leak. - 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.
- 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.
- 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 } - 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.
- What's
finalon a method?✨ Show Answer
Marks a virtual function as not further overridable in subclasses. Lets the compiler devirtualize the call (small optimization).
- 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. - Why prefer
std::unique_ptr<Base>overBase*?✨ Show Answer
Automatic delete on scope exit. Combined with virtual destructor, this is leak-proof polymorphism.
- Show calling base version explicitly from override.
✨ Show Answer
void speak() const override { Animal::speak(); // call base version std::cout << " Woof\n"; } - 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.
- What is
dynamic_cast<Derived*>(basePtr)?✨ Show Answer
Runtime check: if
*basePtractually is a Derived, returns Derived*; otherwisenullptr. Requires at least one virtual function in Base for RTTI to be present. - 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.
- Add a
describe()method to Animal that callsspeak()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.