OOP in C++ — Level 3¶
Polymorphism, Overriding, Virtual Functions & Abstract Classes¶
1. Function Overloading¶
1.1 Definition¶
Function overloading means having multiple functions with the same name but different parameter lists.
class Calculator {
public:
void add(int a, int b) {
cout << a + b;
}
void add(double a, double b) {
cout << a + b;
}
void add(int a, int b, int c) {
cout << a + b + c;
}
};
The compiler determines which function to call based on the arguments.
Calculator c;
c.add(2, 3); // int version
c.add(2.5, 3.5); // double version
c.add(1, 2, 3); // three-argument version
This is compile-time polymorphism.
1.2 What Can Be Different?¶
Overloaded functions can differ in:
Number of parameters¶
void fun(int);
void fun(int, int);
Type of parameters¶
void fun(int);
void fun(double);
Order of parameter types¶
void fun(int, double);
void fun(double, int);
1.3 Return Type Alone Cannot Overload¶
This is invalid:
int fun(int x);
double fun(int x); // ERROR
The parameter list is identical.
The compiler cannot choose between them based only on return type.
Remember¶
Return type is not sufficient for function overloading.
2. const and Function Overloading¶
Member functions can also be overloaded based on whether they are const.
class Test {
public:
void fun() {
cout << "Non-const";
}
void fun() const {
cout << "Const";
}
};
Now:
Test t;
const Test ct;
t.fun(); // Non-const
ct.fun(); // Const
A const object prefers the const member function.
3. Default Arguments + Overloading Trap¶
Consider:
void fun(int x);
void fun(int x, int y = 10);
Now:
fun(5);
This is ambiguous.
Why?
Both functions can accept one argument:
fun(int)
fun(int, int = 10)
So the compiler cannot uniquely determine which one to call.
Placement Trap¶
Default arguments can create ambiguity when combined with overloaded functions.
4. Implicit Conversion During Overloading¶
Suppose:
void fun(int);
void fun(double);
Calling:
fun('A');
'A' is a char.
Both conversions are possible, but conversion to int is a better match than conversion to double.
Therefore:
fun(int);
is selected.
Important¶
The compiler considers the quality of conversions while resolving overloads.
5. Operator Overloading¶
5.1 Definition¶
Operator overloading allows operators such as:
+
-
*
/
==
<
>
++
--
to work with user-defined types.
Example:
class Point {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
Point operator+(const Point& other) {
return Point(x + other.x, y + other.y);
}
};
Now:
Point p1(10, 20);
Point p2(5, 7);
Point p3 = p1 + p2;
The expression:
p1 + p2
is conceptually:
p1.operator+(p2);
6. Binary Operator as a Member Function¶
For:
p1 + p2
when operator+ is a member function:
p1.operator+(p2);
Therefore:
left operand → current object (`this`)
right operand → function parameter
For example:
Point operator+(const Point& other)
Here:
p1 → this
p2 → other
7. Operator Overloading Is Compile-Time Polymorphism¶
Operator overloading is resolved during compilation.
Therefore:
Function overloading
Operator overloading
↓
Compile-time polymorphism
8. Unary Operator Overloading¶
Unary operators operate on one operand.
Examples:
++
--
!
-
Example:
class Number {
public:
int x;
void operator++() {
++x;
}
};
9. Prefix vs Postfix ++¶
C++ distinguishes prefix and postfix increment using a dummy int parameter.
Prefix¶
void operator++() {
++x;
}
Postfix¶
void operator++(int) {
x++;
}
The int is a dummy parameter used only to distinguish postfix from prefix.
10. Operators That Cannot Be Overloaded¶
Some operators cannot be overloaded.
Important examples:
::
.
.*
?:
sizeof
typeid
Also remember:
You cannot create a completely new operator.
You can only overload operators that already exist in C++.
11. Restrictions on Operator Overloading¶
Operator overloading cannot change:
- operator precedence
- operator associativity
- number of operands
For example, you cannot make + behave as if it were a ternary operator.
Also, at least one operand must be a user-defined type when defining an overloaded operator.
You cannot redefine an operator purely for built-in types such as:
int + int
12. Function Overriding¶
12.1 Definition¶
Function overriding occurs when a derived class provides its own implementation of a virtual function inherited from the base class.
Example:
class Base {
public:
virtual void fun() {
cout << "Base";
}
};
class Derived : public Base {
public:
void fun() override {
cout << "Derived";
}
};
Derived::fun() overrides Base::fun().
13. Overriding Requires Compatible Signature¶
Suppose:
class Base {
public:
virtual void fun(int) {}
};
This correctly overrides it:
class Derived : public Base {
public:
void fun(int) override {}
};
But:
void fun(double) override;
does not override it.
Likewise:
void fun() override;
does not override fun(int).
14. const Is Part of the Function Signature for Overriding¶
Consider:
class Base {
public:
virtual void fun() const {}
};
This is not an override:
class Derived : public Base {
public:
void fun() override {}
};
because:
Base: fun() const
Derived: fun()
They are different member-function signatures.
Correct:
void fun() const override {}
15. Return Type in Overriding¶
You cannot arbitrarily change the return type:
class Base {
public:
virtual int fun() {
return 10;
}
};
class Derived : public Base {
public:
double fun() override { // ERROR
return 2.5;
}
};
The return type must be compatible.
There is an advanced exception called covariant return types, involving certain related class pointer/reference return types.
For basic placement preparation:
Do not assume you can change the return type while overriding.
16. Access Modifier and Overriding¶
The access modifier can differ between the base and derived versions.
class Base {
public:
virtual void fun() {}
};
class Derived : public Base {
private:
void fun() override {}
};
This is still an override.
Therefore:
Access control and overriding are separate concepts.
The derived function can be private, even if the base function is public.
17. Once Virtual, It Remains Virtual¶
If:
class Base {
public:
virtual void fun() {}
};
then:
class Derived : public Base {
public:
void fun() override {}
};
Derived::fun() is still virtual.
A further derived class can override it:
class Child : public Derived {
public:
void fun() override {}
};
You don't need to write virtual again.
18. Virtual Functions¶
18.1 Why Virtual Functions?¶
Consider:
class Base {
public:
void fun() {
cout << "Base";
}
};
class Derived : public Base {
public:
void fun() {
cout << "Derived";
}
};
Now:
Base* ptr = new Derived();
ptr->fun();
Output:
Base
because fun() is not virtual.
The call is resolved using the static type:
Base*
18.2 Adding virtual¶
class Base {
public:
virtual void fun() {
cout << "Base";
}
};
Now:
Base* ptr = new Derived();
ptr->fun();
Output:
Derived
This is dynamic dispatch.
19. Static Type vs Dynamic Type¶
Consider:
Animal* ptr = new Dog();
The pointer has:
Static type = Animal*
The actual object is:
Dynamic type = Dog
For a virtual function call, the dynamic type determines which implementation executes.
Animal* ptr
↓
Dog object
↓
virtual function
↓
Dog implementation
20. vtable and vptr¶
The C++ standard does not require a specific implementation using vtable and vptr.
However, most mainstream C++ implementations use a mechanism conceptually similar to this.
vtable¶
A virtual table contains entries associated with virtual functions.
Conceptually:
Base vtable
----------------
fun → Base::fun
Derived:
Derived vtable
----------------
fun → Derived::fun
If Derived does not override another virtual function:
Derived vtable
----------------
fun → Derived::fun
other → Base::other
21. vptr¶
A polymorphic object typically contains a hidden pointer called a vptr.
Conceptually:
Dog object
+----------------+
| vptr ----------|----> Dog vtable
+----------------+
| data members |
+----------------+
The vtable then points to the appropriate virtual functions.
Again, this is an implementation model, not a direct C++ language requirement.
22. Virtual Functions and Object Size¶
Because typical implementations use a hidden vptr, objects containing virtual functions may have additional memory overhead.
For example:
class Base {
public:
virtual void fun() {}
int x;
};
A typical implementation may store:
vptr
x
padding
Therefore, don't blindly calculate object size by simply adding visible data members when virtual dispatch is involved.
23. Static Member Functions Cannot Be Virtual¶
A static member function has no this pointer because it doesn't belong to a particular object.
Virtual dispatch requires an object.
Therefore:
static virtual void fun(); // invalid
Static functions cannot be virtual.
24. Constructors Cannot Be Virtual¶
Constructors cannot be virtual because the object is not fully constructed yet and virtual dispatch requires an already-existing object.
virtual Base(); // invalid
Destructors, however, can be virtual.
Virtual destructors are covered in Level 4.
25. Pure Virtual Functions¶
A pure virtual function is declared using:
= 0
Example:
class Animal {
public:
virtual void sound() = 0;
};
This essentially says:
Derived concrete classes are expected to provide their own implementation.
26. Abstract Class¶
A class containing at least one pure virtual function is an abstract class.
class Animal {
public:
virtual void sound() = 0;
};
You cannot directly create an object:
Animal a; // ERROR
But you can create a pointer/reference:
Animal* ptr;
Animal& ref = dog;
And you can point a base pointer toward a concrete derived object:
Animal* ptr = new Dog();
27. Abstract Class Can Have Normal Functions¶
An abstract class does not have to contain only pure virtual functions.
It can contain:
- data members
- constructors
- destructors
- normal functions
- virtual functions
- pure virtual functions
Example:
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
void info() {
cout << name;
}
virtual void sound() = 0;
};
This is still abstract because it has a pure virtual function.
28. Abstract Class Can Have a Constructor¶
Although:
Animal a;
is invalid, the constructor of Animal can still run when constructing a derived object.
class Animal {
public:
Animal() {
cout << "Animal constructor\n";
}
virtual void sound() = 0;
};
class Dog : public Animal {
public:
Dog() {
cout << "Dog constructor\n";
}
void sound() override {}
};
Dog d;
Output:
Animal constructor
Dog constructor
The base constructor is needed to initialize the base portion of the derived object.
29. Pure Virtual Function Can Have a Definition¶
A pure virtual function can actually have a function body.
class Base {
public:
virtual void fun() = 0;
};
void Base::fun() {
cout << "Base implementation";
}
The class is still abstract.
A derived class can call that implementation explicitly:
class Derived : public Base {
public:
void fun() override {
Base::fun();
cout << " Derived";
}
};
30. Pure Virtual Destructor¶
A destructor can also be pure virtual:
class Base {
public:
virtual ~Base() = 0;
};
But a pure virtual destructor must have a definition:
Base::~Base() {
}
The class becomes abstract, but the base destructor still needs an implementation because the base portion of the object must eventually be destroyed.
Virtual destructors themselves are covered in detail in Level 4.
31. Interface-Like Classes¶
C++ does not have a dedicated:
interface
keyword.
Instead, interface-like behavior is commonly achieved using an abstract class containing mostly/all pure virtual functions.
Example:
class Payment {
public:
virtual void pay() = 0;
virtual void refund() = 0;
virtual ~Payment() {}
};
Different classes can implement the interface:
class UPI : public Payment {
public:
void pay() override {}
void refund() override {}
};
class Card : public Payment {
public:
void pay() override {}
void refund() override {}
};
32. Runtime Polymorphism¶
32.1 Definition¶
Runtime polymorphism means that the implementation to execute is determined at runtime based on the actual object.
The classic C++ pattern is:
Base* ptr = new Derived();
ptr->virtualFunction();
which calls:
Derived::virtualFunction()
33. Requirements for Runtime Polymorphism¶
The classic pattern involves:
- Inheritance
- Virtual function in the base class
- Overriding in derived class
- Base pointer/reference
- Base pointer/reference referring to a derived object
- Virtual function call
Conceptually:
Inheritance
↓
Base pointer/reference
↓
Derived object
↓
Virtual function
↓
Dynamic dispatch
↓
Derived implementation
34. Runtime Polymorphism Using a Pointer¶
class Animal {
public:
virtual void sound() {
cout << "Animal\n";
}
};
class Dog : public Animal {
public:
void sound() override {
cout << "Dog\n";
}
};
Animal* ptr = new Dog();
ptr->sound();
Output:
Dog
Although:
ptr
has type:
Animal*
the actual object is:
Dog
35. Runtime Polymorphism Using a Reference¶
It also works with references.
Dog d;
Animal& ref = d;
ref.sound();
Output:
Dog
Therefore:
Runtime polymorphism can work through both base-class pointers and base-class references.
36. Why Runtime Polymorphism Is Useful¶
Suppose we have:
class Payment {
public:
virtual void pay() = 0;
};
class CreditCard : public Payment {
public:
void pay() override {
cout << "Credit Card\n";
}
};
class UPI : public Payment {
public:
void pay() override {
cout << "UPI\n";
}
};
We can write:
void processPayment(Payment& p) {
p.pay();
}
Then:
CreditCard card;
UPI upi;
processPayment(card);
processPayment(upi);
Output:
Credit Card
UPI
The function doesn't need to know the exact concrete type.
This provides:
- extensibility
- loose coupling
- common interfaces
- substitutability of derived objects
- cleaner object-oriented design
37. Collection of Different Derived Objects¶
Runtime polymorphism is especially useful when multiple derived types need to be handled uniformly.
Animal* animals[2];
Dog d;
Cat c;
animals[0] = &d;
animals[1] = &c;
for(int i = 0; i < 2; i++) {
animals[i]->sound();
}
Output:
Dog
Cat
The array contains Animal*, but the objects can be different derived types.
38. Static Binding vs Dynamic Binding¶
Static Binding¶
Function call is determined at compile time.
class Base {
public:
void fun() {
cout << "Base";
}
};
class Derived : public Base {
public:
void fun() {
cout << "Derived";
}
};
Base* ptr = new Derived();
ptr->fun();
Output:
Base
Because fun() is not virtual.
The compiler uses the static type:
Base*
Dynamic Binding¶
With:
class Base {
public:
virtual void fun() {
cout << "Base";
}
};
Then:
Base* ptr = new Derived();
ptr->fun();
Output:
Derived
The dynamic type of the object determines the implementation.
39. Direct Object Calls¶
Virtual functions do not mean every call automatically requires runtime dispatch.
Consider:
Dog d;
d.sound();
The compiler already knows that d is a Dog.
The interesting runtime-polymorphism situation is:
Animal* ptr = &d;
ptr->sound();
because:
Static type = Animal*
Dynamic type = Dog
40. override¶
The override keyword tells the compiler:
"I intend this function to override a virtual function from the base class. Verify that it actually does."
Example:
class Base {
public:
virtual void show() {}
};
class Derived : public Base {
public:
void show() override {}
};
41. Why override Is Useful¶
Without override, this mistake may go unnoticed:
class Base {
public:
virtual void show(int) {}
};
class Derived : public Base {
public:
void show() {
}
};
The programmer may think they overrode show, but they didn't.
With:
void show() override
the compiler catches the mismatch.
42. override Does Not Create Overriding¶
The keyword does not magically turn a function into an override.
It only asks the compiler to verify the intended override.
So:
virtual → enables virtual dispatch
override → verifies that overriding actually occurs
43. final¶
final prevents further overriding.
Example:
class Base {
public:
virtual void fun() final {
cout << "Base";
}
};
Now:
class Derived : public Base {
public:
void fun() override {} // ERROR
};
because the function is final.
44. final on a Class¶
A class can also be declared final:
class Base final {
};
Now:
class Derived : public Base {
};
is illegal.
Therefore:
final function → cannot override further
final class → cannot inherit from it
45. Combining override and final¶
Both can be used together:
class Base {
public:
virtual void fun() {}
};
class Derived : public Base {
public:
void fun() override final {}
};
Meaning:
Derived::fun()correctly overridesBase::fun()- No class derived from
Derivedcan overridefun()
46. override vs final¶
| Keyword | Meaning |
|---|---|
override |
Verify that this function overrides a base virtual function |
final on function |
Prevent further overriding |
final on class |
Prevent inheritance |
Memory trick:
override → "I am overriding."
final → "Stop here."
47. Common Placement Traps¶
Trap 1 — Same name doesn't mean overriding¶
Base:
fun(int)
Derived:
fun(double)
Not an override.
Trap 2 — const mismatch¶
Base:
fun() const
Derived:
fun()
Not an override.
Trap 3 — Return type mismatch¶
Base:
int fun()
Derived:
double fun()
Invalid overriding.
Trap 4 — Non-virtual function¶
Base* p = new Derived();
p->fun();
If fun() isn't virtual:
Base
If fun() is virtual:
Derived
Trap 5 — override catches mistakes¶
void fun(double) override
when base has:
virtual void fun(int)
→ compilation error.
Trap 6 — final prevents overriding¶
virtual void fun() final;
A derived class cannot override it.
48. Compile-Time vs Runtime Polymorphism¶
| Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|
| Function overloading | Function overriding |
| Operator overloading | Virtual functions |
| Decision at compile time | Decision at runtime |
| Static binding | Dynamic binding |
| No runtime virtual dispatch | Virtual dispatch |
fun(int) vs fun(double) |
Base* → Derived |
49. Mixed Example¶
Consider:
class Base {
public:
virtual void fun() {
cout << "Base ";
}
void show() {
cout << "BaseShow ";
}
};
class Derived : public Base {
public:
void fun() override {
cout << "Derived ";
}
void show() {
cout << "DerivedShow ";
}
};
Now:
Derived d;
Base* ptr = &d;
ptr->fun();
ptr->show();
d.fun();
d.show();
Output:
Derived BaseShow Derived DerivedShow
Why?¶
ptr->fun()¶
fun() is virtual:
Base* → Derived object
↓
dynamic dispatch
↓
Derived::fun()
→ Derived
ptr->show()¶
show() is not virtual:
Base* → Base::show()
→ BaseShow
d.fun()¶
Direct Derived object:
→ Derived::fun()
d.show()¶
Direct Derived object:
→ Derived::show()
50. Most Important Mental Model¶
Whenever you see:
Base* p = &derived;
p->function();
ask:
Step 1¶
Is function() virtual?
If NO:¶
Static binding
→ Base implementation
If YES:¶
Dynamic binding
→ Derived implementation
This simple decision solves a large number of C++ placement questions.
51. Placement Quick Revision¶
Function Overloading¶
Same name
Different parameters
Compile time
Operator Overloading¶
Give existing operators meaning for user-defined types
Compile time
Function Overriding¶
Derived provides implementation of inherited virtual function
Runtime polymorphism when called virtually through base pointer/reference
Virtual Function¶
Enables dynamic dispatch
Pure Virtual Function¶
virtual void fun() = 0;
Creates a contract and makes the class abstract.
Abstract Class¶
Cannot instantiate directly
Can have constructors, destructors, data members and normal functions
Can be used through pointers/references
Runtime Polymorphism¶
Base pointer/reference
+
Derived object
+
Virtual function
=
Dynamic dispatch
override¶
Compiler verifies intended overriding
final¶
Prevents further overriding/inheritance
Level 3 — Final Concept Map¶
POLYMORPHISM
|
+---------------+---------------+
| |
Compile-Time Runtime
| |
+------+-------+ |
| | |
Function Operator Virtual Function
Overloading Overloading |
|
Overriding
|
Base Pointer/Reference
|
Derived Object
|
Dynamic Dispatch
|
Derived Implementation
VIRTUAL / ABSTRACT CONCEPTS
|
+------------+------------+
| |
Virtual Function Pure Virtual Function
| |
Dynamic Dispatch Abstract Class
|
Interface-like Design
OVERRIDING CONTROL
|
+--------+--------+
| |
override final
| |
Verify override Stop overriding
|
final class
|
Stop inheritance
Level 3 — Interview Checklist¶
Before an interview, make sure you can answer these without hesitation:
- What is function overloading?
- Why can't return type alone overload a function?
- How can
constmember functions be overloaded? - How can default arguments cause ambiguity?
- What is operator overloading?
- What does
p1 + p2become conceptually for a memberoperator+? - What operators cannot be overloaded?
- What is function overriding?
- What makes overriding different from hiding?
- Why is
constimportant in overriding? - What does
overridedo? - What does
finaldo? - What is a virtual function?
- What is dynamic dispatch?
- What is static binding vs dynamic binding?
- What are static and dynamic types?
- What are vtable and vptr conceptually?
- Why can't static member functions be virtual?
- Why can't constructors be virtual?
- What is a pure virtual function?
- What is an abstract class?
- Can an abstract class have a constructor?
- Can a pure virtual function have a definition?
- Can a pure virtual destructor have a definition?
- Does C++ have an
interfacekeyword? - What is runtime polymorphism?
- Why are base pointers/references important?
- What happens when a base function is not virtual?
- What happens when a derived function is called directly?
- Why is runtime polymorphism useful in real systems?
- What is the classic
Base* → Derivedpattern?
One-Line Level 3 Summary¶
C++ polymorphism allows the same interface to represent different behaviors: overloading and operator overloading provide compile-time polymorphism, while virtual functions, overriding, and base pointers/references provide runtime polymorphism.