Polymorphism, virtual functions and abstract classes notes — Unit 4
Free unit-wise study notes on polymorphism, virtual functions and abstract classes for Object Oriented Programming with C++, Semester 2 of B.Tech — Computer Science & Engineering — key concepts, examples, important questions and a revision checklist for semester exams.
Master the final pillar of OOP: Polymorphism. This deep-dive uncovers the mechanics of Early vs Late Binding, the internal architecture of V-Tables and V-Pointers, and the design philosophy of Abstract Interfaces.
Notebook — 14 pages
Page 1
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
1. The Nature of Polymorphism
Polymorphism, derived from Greek ('poly' meaning many, 'morph' meaning form), is the ability of an interface or function to execute wildly different behaviors depending on the exact object it is interacting with.
⇒The Conceptual Goal
Imagine an array of generic `Shape` pointers. Some point to `Circle` objects, others to `Square` objects. If you loop through them and call `ptr->draw()`, Polymorphism is the magic that ensures the correct math is executed for each shape automatically, without you writing massive `if-else` type-checking statements.
Single Interface
A universal command like 'draw()' or 'calculatePay()'.
Multiple Implementations
Every specific child class has its own unique execution logic for that command.
Dynamic Resolution
The system automatically routes the command to the correct logic at runtime.
Page 2
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
2. The Binding Timeline
To understand how polymorphism works, you must understand 'Binding'. Binding is the process of linking a function call in your code to the actual memory address of the function's execution block.
⇒1. Early Binding (Static / Compile-Time)
The compiler matches the function call to the memory address DURING the compilation phase. It does this by looking at the strict data types of the variables involved. This is incredibly fast because the decision is hardcoded before the program even runs.
Examples: Normal function calls, Function Overloading, Operator Overloading.
Limitation: It is rigid. If a pointer's type changes dynamically while the program is running, Early Binding will fail to adapt.
⇒2. Late Binding (Dynamic / Run-Time)
The compiler CANNOT determine which function to call. It leaves a mechanism in the compiled code to figure it out dynamically while the program is actually running in RAM.
Examples: Virtual Functions.
Limitation: Slight performance overhead due to the runtime lookup calculations.
Page 3
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
3. The Base Pointer Anomaly
To achieve Late Binding, we must use Pointers. A fundamental rule of C++ is that a Pointer to a Base Class can legally hold the address of a Derived Class object.
Base *bptr;
Derived d_obj;
bptr = &d_obj; // PERFECTLY LEGAL
⇒The Early Binding Trap
Look at the code below. Because C++ uses Early Binding by default, the compiler looks at the TYPE of the pointer (`Base*`). It completely ignores what the pointer is actually pointing at in RAM.
class Base {
public: void show() { cout << "Base"; }
};
class Derived : public Base {
public: void show() { cout << "Derived"; }
};
int main() {
Base *bptr = new Derived();
bptr->show(); // PRINTS "Base"! (Early Binding Trap)
}
We wanted it to print 'Derived', but because `bptr` is declared as a `Base*`, Early Binding hardcoded the call to `Base::show()` during compilation.
Page 4
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
4. Virtual Functions
To fix the Early Binding Trap and achieve true Runtime Polymorphism, we use the `virtual` keyword. It disables Early Binding for that specific function.
Solving the anomaly with Virtual
class Base {
public:
// The virtual keyword forces Late Binding!
virtual void show() { cout << "Base"; }
};
class Derived : public Base {
public:
// Overriding the virtual function
void show() { cout << "Derived"; }
};
int main() {
Base *bptr = new Derived();
bptr->show(); // NOW PRINTS "Derived"!
}
When `show()` is marked virtual, the compiler stops looking at the TYPE of the pointer (`Base*`). Instead, at runtime, it physically checks the RAM to see what object the pointer is aimed at (`Derived`), and executes the function belonging to that actual object.
Page 5
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
5. Rules for Virtual Functions
Because Virtual Functions fundamentally alter how the compiler generates execution jumps, there are strict syntactical and logical rules governing them.
Must be Members: They must be member functions of a class. They cannot be static functions or global functions.
Access via Pointers: Runtime polymorphism is ONLY achieved when a virtual function is called through a pointer or a reference. Calling it directly on an object `obj.show()` falls back to early binding.
Identical Signatures: The prototype (return type, name, parameters) in the Base class and the Derived class MUST match exactly. If they differ even slightly, C++ treats it as Function Hiding, not overriding.
Virtual Base is Enough: If a function is declared virtual in the base class, it remains virtual in all derived classes down the chain, even if you don't explicitly write `virtual` again.
No Virtual Constructors: You cannot mark a constructor as virtual. An object needs a definite type to be created.
Virtual Destructors Allowed: You absolutely can, and should, have virtual destructors.
Page 6
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
6. Under the Hood: V-Tables
How does the computer 'check the RAM at runtime'? C++ implements Late Binding using an ingenious, highly optimized mechanism involving V-Tables and V-Pointers.
⇒1. The V-Table (Virtual Table)
The moment the compiler sees the word `virtual` in a class, it generates a hidden, static array for that class called the V-Table. This array holds the absolute memory addresses of the virtual functions for that specific class.
`Base` gets a V-Table pointing to `Base::show()`
`Derived` gets its own V-Table pointing to `Derived::show()`
⇒2. The V-Ptr (Virtual Pointer)
The compiler also secretly adds a hidden pointer variable (the `_vptr`) inside EVERY object created from these classes. This `_vptr` points directly to the V-Table of the class that created the object.
Page 7
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
7. The Runtime Lookup Process
Let's trace exactly what happens inside the CPU when `bptr->show()` is executed at runtime.
1. CPU executes bptr->show()
2. bptr goes to the Object in RAM
3. Extracts the hidden _vptr from the Object
4. _vptr leads to the Derived Class V-Table
5. V-Table provides the memory address of Derived::show()
6. CPU jumps to that address and executes!
The Dynamic Dispatch Pipeline
⇒The Performance Cost
As you can see, this requires two extra memory hops (read `_vptr`, then read V-Table) compared to Early Binding. This is why C++ doesn't make functions virtual by default. It adheres to the philosophy: 'You don't pay for what you don't use'.
Page 8
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
8. Virtual Destructors
We established that Constructors cannot be virtual, but Destructors MUST be virtual when dealing with polymorphism. Failing to do so causes massive memory leaks.
The Memory Leak Scenario
class Base {
public: ~Base() { cout << "Base destroyed"; }
};
class Derived : public Base {
public: ~Derived() { cout << "Derived destroyed"; }
};
int main() {
Base *bptr = new Derived();
delete bptr; // Early Binding Trap!
}
Because the destructor `~Base()` is NOT virtual, Early Binding executes. It only destroys the Base portion of the object. The Derived portion (and any heap memory it allocated) is permanently orphaned in RAM.
⇒The Fix
Simply write `virtual ~Base()`. This triggers Late Binding. The system will look at the actual object, execute `~Derived()` first, which then automatically chains up to `~Base()`, cleanly erasing the entire object.
Page 9
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
9. Pure Virtual Functions
Sometimes, a Base class represents a concept so broad that providing an implementation for a virtual function makes no sense. For example, a generic `Shape` class has a `draw()` function, but what does a generic shape look like? It doesn't.
⇒The Syntactical Zero
A Pure Virtual Function is a function that explicitly has NO implementation in the base class. We declare it by assigning `= 0` to the prototype.
class Shape {
public:
// Pure Virtual Function
virtual void draw() = 0;
};
⇒The Contract
A pure virtual function acts as a strict contract. It tells the compiler: 'Any class that inherits from this MUST write their own implementation of this function. If they refuse, the compiler will throw an error'.
Page 10
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
10. Abstract Classes
The moment a class contains AT LEAST ONE Pure Virtual Function, it ascends to a new status: It becomes an Abstract Class.
⇒The Instantiation Ban
You absolutely CANNOT create an object of an Abstract Class. The compiler will block you.
Why? Because an object represents a complete, functioning entity in memory. Since `Shape` contains a function `draw() = 0` with no code, what would happen if the CPU tried to execute `s.draw()`? The program would crash trying to execute nothing.
⇒Pointers are Legal
While you cannot create objects of an abstract class, you CAN create pointers to them! `Shape *ptr;` is perfectly legal, and is the entire basis of polymorphic arrays.
Page 11
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
11. Abstract Class Architecture
Abstract classes serve as the ultimate 'Interface' in system design. They define WHAT an object can do, without dictating HOW it does it.
A Complete Polymorphic System
// Abstract Interface
class Logger {
public:
virtual void log(string msg) = 0;
virtual ~Logger() {} // Crucial!
};
// Concrete Class 1
class FileLogger : public Logger {
public:
void log(string msg) { cout << "Writing to file: " << msg; }
};
// Concrete Class 2
class ConsoleLogger : public Logger {
public:
void log(string msg) { cout << "Printing to screen: " << msg; }
};
In a massive enterprise system, the core logic only ever interacts with `Logger*`. It doesn't care if the underlying object is a `FileLogger` or `ConsoleLogger`. The architecture is completely decoupled and infinitely extensible.
Page 12
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
12. The 'this' Pointer Revisited
We mentioned the `this` pointer in Unit 1 as the hidden parameter passed to all non-static member functions. Now we can examine its mechanical role in polymorphism and chaining.
⇒Object Identity
The `this` pointer strictly holds the memory address of the object that invoked the function. Its type is a constant pointer: `ClassType* const this`.
Resolving Shadowing and Chaining
class Box {
int size;
public:
// 1. Resolving Shadowing (Parameters matching member names)
Box* setSize(int size) {
this->size = size;
// 2. Returning the object for Method Chaining
return this;
}
};
int main() {
Box b;
// Method chaining possible because setSize returns the object pointer!
b.setSize(10)->setSize(20);
}
Page 13
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
13. Binding Summary Table
A direct comparison of the two binding mechanisms is the most frequent university exam question in this unit.
Early vs Late Binding
Feature
Early Binding (Static)
Late Binding (Dynamic)
Resolution Time
During Compilation
During Runtime
Mechanism
Based on Pointer TYPE
Based on Object DATA in RAM
Implementation
Overloaded functions, Operators
Virtual Functions
Execution Speed
Extremely fast
Slightly slower (V-Table lookup)
Flexibility
Rigid. Cannot adapt dynamically.
Highly adaptable. Core to design patterns.
Page 14
Wink Notes
B.Tech CSE — 2nd Semester
Object Oriented Programming with C++
— Unit - 4 —
14. Unit 4 Revision Checklist
⇒End-of-Unit Verification
Define Polymorphism and clearly contrast Compile-time vs Run-time polymorphism.
Write the exact output of a program demonstrating the 'Base Pointer Anomaly' (calling a non-virtual function via a base pointer).
Explain how the `virtual` keyword alters the compiler's behavior from Early Binding to Late Binding.
List the 6 strict rules for declaring and using virtual functions.
Draw the internal architecture of V-Tables and V-Pointers, explaining the two-hop memory lookup.
Explain exactly why a Virtual Destructor is necessary, and write code proving the memory leak that occurs without one.
Define a Pure Virtual Function syntactically and logically.
Explain the two primary rules of an Abstract Class (instantiation ban, interface requirement).
Write a full polymorphic program with one Abstract base class and two concrete derived classes.