OOPs – UNIT 4 : Part 3
Polymorphism:
Polymorphism in C++ is the ability of a function, object, or operator
to behave differently in different contexts.
• Polymorphism means "many forms" — one function or object behaves
differently based on context.
• In C++, it allows to use the same function name or operator in
different ways.
• It helps in code flexibility and reusability.
• Polymorphism makes it easier to extend and maintain programs.
• There are two types of polymorphism:
• Compile-time polymorphism (also called static polymorphism) —
achieved through function overloading and operator overloading.
• Run-time polymorphism (also called dynamic polymorphism) — achieved
using inheritance and virtual functions.
POLYMORPHISM in C++
|
|
------------------------------------------
| |
Compile-Time Polymorphism Run-Time Polymorphism
(Static/Early Binding) (Dynamic/Late Binding)
| |
--------------------- ---------------------
| | | |
Function Operator Virtual Functions Function
Overloading Overloading Overriding
Function Overloading :
read from Unit 2 Notes :
https://www.gecsheikhpura.org.in/wp-
content/uploads/sites/18/2025/05/file_68334b0298239.pdf
Operator Overloading :
Operator overloading allows to redefine how operators work for user-
defined types (like objects of a class).
• It allows operators to work with objects, not just built-in data types.
• It is done using special functions called operator functions.
• Makes the code easier to read and more intuitive.
• Only existing operators can be overloaded; new operators cannot be
created.
• Some operators like ::, :?, sizeof, and typeid cannot be overloaded.
Syntax:
return_type operator symbol (parameters) {
// your custom code here
}
P a g e 1 | 14 OOPs. GEC_SHK
Example:
#include <iostream>
using namespace std;
class Complex {
float real, imag;
public:
Complex(float r = 0, float i = 0) {
real = r;
imag = i;
}
// Overload '+' operator
Complex operator + (const Complex& obj) {
return Complex(real + obj.real, imag + obj.imag);
}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(2.5, 3.5), c2(1.2, 4.8);
Complex c3 = c1 + c2; // Using overloaded +
c3.display(); // Output: 3.7 + 8.3i
return 0;
}
Operator can be overloaded using friend function :
When to Use Friend Functions
1. When the left operand is not of your class type
Case: Stream Insertion (<<) and Extraction (>>) Operators
class Student {
string name;
public:
Student(string n) : name(n) {} // another way to initialize
// MUST be a friend function (cannot be a member)
friend ostream& operator<<(ostream& os, const Student& s);
};
// Definition
ostream& operator<<(ostream& os, const Student& s) {
return os << s.name; // Accesses private 'name'
}
P a g e 2 | 14 OOPs. GEC_SHK
2. When We Need Symmetric Operator Overloading
Case: Mixed-Type Arithmetic (e.g., 5 + obj vs. obj + 5)
class Complex {
double real, imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// Friend allows both (obj + 5) and (5 + obj)
friend Complex operator+(const Complex& a, const Complex& b);
};
// Definition
Complex operator+(const Complex& a, const Complex& b) {
return Complex(a.real + b.real, a.imag + b.imag);
}
int main() {
Complex c(1, 2);
Complex c1 = c + 5; // Works (implicit conversion)
Complex c2 = 5 + c; // Also works (only possible with friend)
}
3. When We Need Access to Private Members of Multiple Classes
Case: Comparing Objects from Different Classes
class Engine; // Forward declaration
class Car {
int horsepower;
friend bool operator==(const Car&, const Engine&);
};
class Engine {
int power;
friend bool operator==(const Car&, const Engine&);
};
// Definition
bool operator==(const Car& car, const Engine& engine) {
// Accesses private members of both classes
return car.horsepower == engine.power;
}
Why Can’t These Cases Use Member Functions?
Problem with Member Why Friend is
Scenario
Function Required
Left operand is not Member functions require
Friend allows any
your class (cout << the left operand to
left operand.
obj) be this.
Member functions force the
Symmetric operators Friend allows
left operand to be the
(a + b vs. b + a) flexibility.
class object.
Access to private A member function can only
Friend can access all
members of multiple access its own class’s
declared friends.
classes privates.
P a g e 3 | 14 OOPs. GEC_SHK
Why Friend Functions Cannot Overload the Assignment Operator (=)
The assignment operator (=) must be overloaded as a member function in C++ and
cannot be a friend function. Here's why:
Key Reasons
1. Language Specification Requirements
• The C++ standard explicitly requires the assignment operator to be a non-
static member function
• This is a syntactic constraint built into the language
2. Natural Semantics of Assignment
• Assignment fundamentally modifies the left-hand operand (this object):
• obj1 = obj2; // Modifies obj1, not obj2
• Member functions have direct access to this
• Friend functions don't have a this pointer
3. Automatic Generation by Compiler
• The compiler always generates a default assignment operator if we don't
declare one
• This automatic generation only happens for member functions
• If assignment were allowed as a friend, it would create ambiguity with
compiler-generated versions
4. Chaining Behaviour
• Assignment supports chaining:
• a = b = c; // Equivalent to a = (b = c)
• This requires the operator to return *this by reference
• Only member functions can naturally return *this
P a g e 4 | 14 OOPs. GEC_SHK
Virtual Function:
• A virtual function is a member function in a base class
• declared with the virtual keyword
• can be overridden by derived classes.
• It enables runtime polymorphism, meaning the correct function is called
based on the actual object type.
Why Do We Need Virtual Functions?
1. To Achieve Runtime Polymorphism
• Without virtual, function calls are resolved at compile-time (static
binding).
• With virtual, they’re resolved at runtime (dynamic binding).
Example:
class Animal {
public:
virtual void speak() { cout << "Animal sound" << endl; } // Virtual
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; } // Overrides
Animal::speak()
};
int main() {
Animal* animal = new Dog();
animal->speak(); // Calls Dog::speak() (not Animal::speak())
delete animal;
return 0;
}
Output:
Woof! (Because speak() is virtual.)
Without virtual, this would output "Animal sound" (which is incorrect behaviour).
2. To Support Interface Abstraction
• Virtual functions allow base classes to define interfaces while letting
derived classes provide implementations.
• Pure virtual functions (= 0) enforce that derived classes must override
them.
• Example of Abstract Class:
class Shape {
public:
virtual void draw() = 0; // Pure virtual (must be overridden)
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing a circle" << endl; }
};
int main() {
Shape* shape = new Circle();
shape->draw(); // Calls Circle::draw()
delete shape;
return 0;
}
How Virtual Functions Work (Under the Hood)
P a g e 5 | 14 OOPs. GEC_SHK
1. Virtual Function Table (vtable)
o Each class with virtual functions has a hidden table of function
pointers.
o Objects store a pointer to their class’s vtable.
2. Dynamic Dispatch
o When calling animal->speak(), the program:
1. Looks up the vtable for the actual object type (Dog, Cat,
etc.).
2. Calls the correct function from the vtable.
Key Takeaways :
Feature Non-Virtual Function Virtual Function
Binding Compile-time (static) Runtime (dynamic)
Overriding Hides base function Properly overrides
Performance Faster (no overhead) Slightly slower (vtable lookup)
Use Case Static behavior Polymorphism, interfaces
P a g e 6 | 14 OOPs. GEC_SHK
Function Overriding:
• Function overriding is a feature of object-oriented programming
• allows a derived class to provide a specific implementation of a function
that is already defined in its base class.
Key Characteristics:
1. Inheritance Requirement: Overriding requires an inheritance relationship
between classes (base and derived classes)
2. Same Signature: The function in the derived class must have exactly the
same name, return type, and parameters as in the base class
3. Runtime Polymorphism: The correct function is selected at runtime based
on the actual object type (dynamic binding)
Example:
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { // Virtual function
cout << "Base class show()" << endl;
}
};
class Derived : public Base {
public:
void show() override { // Overrides Base's show()
cout << "Derived class show()" << endl;
}
};
int main() {
Base* b; // Base class pointer
Derived d; // Derived class object
b = &d;
b->show(); // Calls Derived's show() due to overriding
return 0;
}
🤔 Why writing Base* b and calling (using base class) why not Derived d and
directly make a call (using d.show()):
The difference between using Base* b = new Derived() and directly
creating Derived d is about polymorphism and flexibility in design.
1. Direct Object Creation (No Polymorphism)
Derived d;
d.show(); // Always calls Derived::show()
• Behavior: Always calls Derived's version (no ambiguity).
• Limitation:
o You can only use Derived's interface.
o If you later want to switch to a different derived class
(e.g., Derived2), you must rewrite the code.
2. Base Pointer to Derived Object (Polymorphism)
Base* b = new Derived(); // Base pointer, Derived object
b->show(); // Calls Derived::show() if show() is virtual
P a g e 7 | 14 OOPs. GEC_SHK
Why This is Powerful:
• Runtime Polymorphism:
o The correct function (Base::show() or Derived::show()) is
decided at runtime based on the actual object type.
o Enables dynamic behavior (e.g., plugins, event handlers).
• Flexibility:
o You can switch implementations without changing client code:
Base* b;
if (condition)
b = new Derived1();
else
b = new Derived2();
b->show(); // Calls the correct version
3. Interface Programming:
• You program to the base class interface, not the implementation
• Example:
A Shape base class with draw(), where Circle and Square override it.
Approach Binding Flexibility Use Case
When you know the
Derived d; Static (compile-
Low exact type at
d.show(); time)
compile time.
When you need
Base* b = new
polymorphism or
Derived(); b- Dynamic (runtime) High
runtime
>show();
decisions.
Imagine a game with different enemies:
class Enemy { public: virtual void attack() = 0; };
class Dragon : public Enemy { void attack() override { ... } };
class Orc : public Enemy { void attack() override { ... } };
// Game logic doesn't care about the exact enemy type:
Enemy* enemy = getRandomEnemy(); // Could return Dragon or Orc
enemy->attack(); // Calls the correct attack() dynamically
Here, polymorphism is essential—the game engine doesn’t need to know the
concrete enemy type.
Example:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() { cout << "Generic Shape" << endl; }
};
class Circle : public Shape {
public:
P a g e 8 | 14 OOPs. GEC_SHK
void draw() override { cout << "Circle ○" << endl; }
};
class Square : public Shape {
public:
void draw() override { cout << "Square □" << endl; }
};
int main() {
Circle circle;
Square square;
Shape* shape = &circle; // Pointer to Circle
shape->draw(); // Output: Circle ○
shape = □ // Now points to Square
shape->draw(); // Output: Square □
return 0;
}
P a g e 9 | 14 OOPs. GEC_SHK
What is Virtual in C++ ?
The virtual keyword in C++ enables dynamic polymorphism by allowing a function
to be overridden in derived classes. It -
1. Enables Runtime Binding
o Without virtual: Function calls are resolved at compile-time based on
the pointer/reference type.
o With virtual: Function calls are resolved at runtime based on
the actual object type.
2. Allows Function Overriding
o A derived class can provide its own implementation of the function.
o The override specifier (C++11) ensures correctness but is optional.
3. Creates a "Virtual Function Table" (vtable)
o The compiler generates a hidden table of function pointers for each
class with virtual methods.
o Objects store a pointer to their class’s vtable, enabling runtime
dispatch.
Example (without virtual):
class Shape {
public:
void draw() { cout << "Generic Shape" << endl; } // Not virtual
};
class Circle : public Shape {
public:
void draw() { cout << "Circle ○" << endl; } // Hides Shape::draw()
};
int main() {
Shape* shape = new Circle();
shape->draw(); // Calls Shape::draw() (compile-time binding)
delete shape;
return 0;
}
Output:
Generic Shape
Example (with virtual):
class Shape {
public:
virtual void draw() { cout << "Generic Shape" << endl; } // Now virtual
};
class Circle : public Shape {
public:
void draw() override { cout << "Circle ○" << endl; } // Proper override
};
int main() {
Shape* shape = new Circle();
shape->draw(); // Calls Circle::draw() (runtime binding)
delete shape;
return 0;
}
Output:
Circle ○
P a g e 10 | 14 OOPs. GEC_SHK
Pure Virtual Function:
•
a Pure Virtual Function is a virtual function
•
declared in a base class but is meant to be overridden in derived
classes.
• It is used to create an abstract class.
• Syntax
virtual returnType functionName() = 0;
The = 0 part makes the function pure virtual.
Purpose :
1. Force Derived Classes to provide their own implementation.
2. Define interfaces (contracts) that derived classes must follow.
3. Achieve abstraction by hiding implementation details.
Example:
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};
class Circle : public Shape {
public:
void draw() override { // Must implement pure virtual function
cout << "Drawing a circle";
}
};
int main() {
Shape* s = new Circle();
s->draw(); // Calls Circle::draw()
delete s;
return 0;
}
Key Differences
Feature Virtual Function Pure Virtual Function
Syntax virtual void func(); virtual void func() = 0;
Yes (base class provides No (derived classes must
Has Implementation?
default) implement)
Base Class
Yes No (abstract class)
Instantiable?
Mandatory overriding
Purpose Optional overriding
(interface)
P a g e 11 | 14 OOPs. GEC_SHK
Abstract Class :
• an abstract class is a class that cannot be instantiated
• designed to be a base class for other classes.
• It contains at least one pure virtual function.
• Example:
class AbstractClass {
public:
virtual void display() = 0; // Pure virtual function
};
•
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() = 0; // Pure virtual function
};
class Dog : public Animal {
public:
void sound() override {
cout << "Bark" << endl;
}
};
int main() {
// Animal a; // Error: Cannot instantiate abstract class
Dog d; // Dog is concrete because it overrides all pure virtual functions
d.sound(); // Output: Bark
return 0;
}
Abstract Class:
• Cannot be directly instantiated :
o we cannot create objects of it.
class Shape {
public:
virtual void draw() = 0; // Pure virtual
};
int main() {
Shape s; // Error: Cannot instantiate abstract class
return 0;
}
• Derived classes must override all pure virtual functions to become
concrete.
o If a derived class does not override all pure virtual functions
of its base class, then that derived class also becomes an
abstract class:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};
P a g e 12 | 14 OOPs. GEC_SHK
class Circle : public Shape {
// No override of draw()
};
int main() {
// Circle c;
// Error: Circle is still abstract because it didn't override draw()
return 0;
}
To make it concrete derived class must override all pure virtual
o
functions:
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
• Must have at least one is required (virtual func() = 0;)
• Abstract classes can also contain normal (non-pure) methods
Interface :
An interface in C++ is typically an abstract class where:
• All member functions are pure virtual (= 0)
• No data members (ideally)
• Meant to define a contract that derived classes must follow
• Example
#include <iostream>
using namespace std;
class IPrintable { // 'I' prefix is a common naming convention for interfaces
public:
virtual void print() = 0; // Pure virtual function
virtual ~IPrintable() {} // Always add a virtual destructor in interfaces
};
class Document : public IPrintable {
public:
void print() override {
cout << "Printing document..." << endl;
}
};
• Characteristics:
Property Description
All methods are pure
Each function has = 0
virtual
Interfaces should not define function bodies (except
No implementation allowed
destructor)
No data members Keep the interface clean and behavior-focused
Cannot be instantiated Interfaces are abstract classes
Supports multiple
A class can implement multiple interfaces
inheritance
P a g e 13 | 14 OOPs. GEC_SHK
Comparison Table
Class with at least Abstract class with Class with all
Definition one pure virtual only pure virtual functions
function functions implemented
Can be
instantiated? ❌ No ❌ No ✅ Yes
Can have data ⚠️ Not recommended
members? ✅ Yes ✅ Yes
(ideally no data)
Pure virtual
functions? ✅ At least one ✅ All functions ❌ None
Normal member ❌ No (only pure
functions? ✅ Yes (optional) ✅ Yes
virtual functions)
Base for Define strict
Full implementation,
Use case polymorphism or contracts (like
used directly
partial abstraction APIs)
Multiple
inheritance ✅ Yes ✅ Yes ✅ Yes
support
Virtual ✅ Required (for ✅ Optional (but
destructor? ✅ Recommended
proper cleanup) good practice)
Example:
// Interface
class IShape {
public:
virtual void draw() = 0;
virtual ~IShape() {} // always add virtual destructor
};
// Abstract Class
class Shape {
public:
virtual void draw() = 0;
void move() { cout << "Moving shape" << endl; }
};
// Concrete Class
class Circle : public IShape {
public:
void draw() override {
cout << "Drawing Circle" << endl;
}
};
P a g e 14 | 14 OOPs. GEC_SHK