optional · CSS study notes
11. Method Over-Riding
- A derived class provides its own implementation of a method already defined in its base class, using the same function signature (name, parameters, return type)
- Distinct from overloading: overriding happens across base/derived classes with an identical signature; overloading happens within the same class with different signatures
- To get true runtime dispatch (the derived version being called even through a base-class pointer), the base method must be declared
virtual— see the Polymorphism page
class Animal
{
public:
void speak() { cout << "Some sound\n"; }
};
class Dog : public Animal
{
public:
void speak() { cout << "Bark!\n"; } // overrides Animal::speak()
};
Dog d;
d.speak(); // "Bark!" -- Dog's version is used
Overriding vs Overloading — the distinction examiners test most
| Overloading | Overriding | |
|---|---|---|
| Where | Same class (or same scope) | Base class vs derived class |
| Signature | MUST differ (params/types) | MUST be identical |
| Resolved | Compile-time | Runtime (if virtual) or compile-time (if not) |
| Purpose | Same operation, different input types | Same operation, specialized for a subclass |
class Printer
{
public:
void print(int x) { cout << "int: " << x; } // OVERLOADING --
void print(string s) { cout << "string: " << s; } // -- same class, different signatures
};
class Animal
{
public:
void speak() { cout << "Some sound\n"; }
};
class Dog : public Animal
{
public:
void speak() { cout << "Bark!\n"; } // OVERRIDING -- same signature, different class
};
Overriding WITHOUT virtual — the trap
class Animal { public: void speak() { cout << "Some sound\n"; } };
class Dog : public Animal { public: void speak() { cout << "Bark!\n"; } };
Animal* a = new Dog();
a->speak(); // prints "Some sound" -- NOT "Bark!" -- because speak() isn't virtual, static binding applies
Without virtual, the compiler decides which speak() to call based on the pointer's declared type (Animal*), not the actual object type (Dog). This is called static binding, and it's the #1 reason overriding "doesn't work" for students — see the Virtual Function page for the fix.
Important MCQs
- Overriding requires the derived class method to have:
- a) A different name b) The exact same signature (name, parameters, return type) as the base method c) No parameters d) A different return type only
- Without the
virtualkeyword on the base method, calling it through a base-class pointer to a derived object uses:- a) Dynamic binding b) Static binding — the pointer's declared type decides, not the actual object type c) No binding at all d) Runtime polymorphism
- Overloading differs from overriding mainly because overloading:
- a) Requires inheritance b) Happens within the same class/scope with different signatures c) Is resolved at runtime d) Requires virtual functions
✅ Self-Test Questions (with Answers)
Q1. What's the key structural difference between overloading and overriding?
Ans: The key structural difference is where the functions live and whether their signatures match. Overloading puts two or more functions with the same name but different signatures in the same class or scope; no inheritance is involved, and the compiler chooses between them at compile time by matching argument types. Overriding puts a function with an identical signature in a derived class, replacing the base class's version; it requires inheritance, and with virtual the choice is made at run time from the object's actual type.
| Aspect | Overloading | Overriding |
|---|---|---|
| Where | Same class or scope | Base class vs derived class |
| Signature | MUST differ (number or type of parameters) | MUST be identical, including const |
| Inheritance needed? | No | Yes |
| Binding | Compile time (static / early) | Run time (dynamic / late), if virtual |
| virtual keyword | Irrelevant | Required in the base for true dispatch |
| Polymorphism type | Compile-time (ad-hoc) | Run-time (subtyping) |
| Return type | May differ, but cannot differ ALONE | Must match (or be covariant) |
// OVERLOADING -- one class, different signatures, chosen by the compiler
class Calculator {
public:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
int add(int a, int b, int c){ return a + b + c; }
};
Calculator c;
c.add(2, 3); // int version -- picked at COMPILE time
c.add(2.5, 3.5); // double version
// OVERRIDING -- two classes, identical signature, chosen by the object
class Animal { public: virtual void sound() { cout << "generic\n"; }
virtual ~Animal() = default; };
class Dog : public Animal { public: void sound() override { cout << "Bark\n"; } };
Animal* a = new Dog();
a->sound(); // "Bark" -- picked at RUN time via the vtable
delete a;
MCQ traps: overloading is compile-time, overriding is run-time — the single most repeated pair in CSS papers. Two functions differing only in return type are not valid overloads. A derived function whose signature differs even slightly (a missing const, a different parameter type) hides rather than overrides — use override so the compiler rejects the mistake. To prevent overriding, use final (virtual void display() final;, or class Vehicle final).
Q2. In the trap example above, why does a->speak() print "Some sound" instead of "Bark!"?
Ans: Because Animal::speak() is not declared virtual, so the call is resolved by static (early) binding. At compile time the compiler sees only the declared type of the pointer — Animal* — and hard-codes a direct call to Animal::speak. It never consults the object's real type at run time, so the fact that a actually points at a Dog is irrelevant. Without virtual, Dog::speak() does not override Animal::speak() at all; it merely hides it (name hiding), and the hidden version is reachable only through a Dog-typed expression.
class Animal { public: void speak() { cout << "Some sound\n"; } }; // no 'virtual'
class Dog : public Animal { public: void speak() { cout << "Bark!\n"; } }; // HIDES, not overrides
Animal* a = new Dog();
a->speak(); // "Some sound" <- pointer type decides (static binding)
Dog* d = new Dog();
d->speak(); // "Bark!" <- Dog-typed expression sees Dog's version
static_cast<Animal*>(d)->speak(); // "Some sound" again -- proof it is the TYPE that decides
With virtual added to the base, the compiler instead emits an indirect call through the object's hidden vptr into its class's vtable, which holds Dog::speak's address — so a->speak() prints "Bark!". That is dynamic (late) binding.
Related traps worth naming:
- Object slicing — even with
virtual, passing or assigning by value (Animal x = *d;) copies only theAnimalpart, discarding theDogdata and its vptr, so the base version runs. Polymorphism requires a pointer or reference. - Signature mismatch — a derived function that differs even slightly (for example missing a
const) does not override; it hides. Marking itoverridemakes the compiler catch this. - Non-virtual destructor — the same static-binding rule means
delete a;would call only~Animal(), leaking theDogpart.
Q3. What single keyword fixes the trap example, and where does it need to be added?
Ans: The keyword is virtual, and it must be added to the base class declaration — virtual void speak() in Animal. That placement is the whole point of the question: adding virtual to Dog::speak() instead achieves nothing for this call, because the compiler decides how to dispatch by looking at the static type of the expression, which is Animal*. Only if Animal::speak() is virtual does the compiler emit an indirect call through the vtable rather than a direct call to Animal::speak.
class Animal {
public:
virtual void speak() { cout << "Some sound\n"; } // <-- 'virtual' goes HERE, in the BASE
virtual ~Animal() = default; // and give a polymorphic base a virtual dtor
};
class Dog : public Animal {
public:
void speak() override { cout << "Bark!\n"; } // 'override' is optional but recommended
};
Animal* a = new Dog();
a->speak(); // "Bark!" <- now late-bound through the vtable
delete a; // calls ~Dog() then ~Animal(), because the destructor is virtual
Points that earn extra marks:
virtualis inherited. OnceAnimal::speak()is virtual,Dog::speak()is automatically virtual too, even without the keyword — so it never needs repeating down the chain.overrideis not the fix. It is a C++11 safety check: it makes the compiler verify that the function really does override a base virtual, catching typos and signature mismatches such as forgetting aconst. Without a virtual base function,overrideis itself a compile error.- Add a
virtualdestructor too. Any class used polymorphically needsvirtual ~Animal(), ordelete a;on aDogcalls only~Animal()— undefined behaviour and a leak. - Cost: each polymorphic object carries a hidden vptr, and each such class has one shared vtable; the call becomes one extra indirection and can no longer be inlined.
MCQ trap: a constructor can never be virtual (the vptr is installed by the constructor, so there is nothing to dispatch through), whereas a destructor in a base class should be. Also note virtual here is unrelated to virtual inheritance, which solves the diamond problem.
📌 Sample & Repeated FPSC Questions (2016–2026)
2026, Paper I, Q4(c) (8 marks) — Demonstrate method overriding using a base class Account and a derived class Current Account. Make balance a protected data member.
class Account
{
protected:
double balance;
public:
Account(double b) : balance(b) {}
virtual void withdraw(double amount)
{
if (amount <= balance) balance -= amount;
else cout << "Insufficient funds.\n";
}
};
class CurrentAccount : public Account
{
double overdraftLimit;
public:
CurrentAccount(double b, double od) : Account(b), overdraftLimit(od) {}
void withdraw(double amount) override // overrides Account::withdraw
{
if (amount <= balance + overdraftLimit) balance -= amount;
else cout << "Withdrawal exceeds overdraft limit.\n";
}
};
Account* acc = new CurrentAccount(1000, 500);
acc->withdraw(1300); // dispatches to CurrentAccount::withdraw() -- virtual + override
Key reasoning points: withdraw() has the identical signature in both classes (true overriding, not overloading); it must be virtual in the base for the call through Account* acc to resolve to CurrentAccount::withdraw() at runtime instead of Account::withdraw().
Repeated question (2021, Q6(b), 6 marks & 2024, Q5(b)/(c), 6–8 marks) — Differentiate/illustrate between overriding and overloading, with an example; and, how do you prevent a method from being overridden?
This exact contrast has been asked at least twice in recent papers — memorize it cold.
| Overloading | Overriding | |
|---|---|---|
| Where | Same class/scope | Base class vs. derived class |
| Signature | Must differ | Must be identical |
| Binding | Compile-time (static) | Runtime (dynamic), if virtual |
| Requires inheritance? | No | Yes |
class Calculator {
public:
int add(int a, int b) { return a + b; } // overloading
double add(double a, double b) { return a + b; }
};
class Animal { public: virtual void sound() { cout << "..."; } };
class Dog : public Animal { public: void sound() override { cout << "Bark"; } }; // overriding
To prevent a method (or class) from being overridden/extended, C++11 onward uses the final specifier — a genuinely useful point that's easy to overlook since it doesn't come up elsewhere on this page:
class Vehicle final { }; // cannot be inherited from at all
class Base {
public:
virtual void display() final { } // cannot be overridden by any derived class
};
(Java's equivalent is the final keyword on a class or method.) Attempting class SportsCar : public Vehicle {} or overriding display() in a derived class is a compile-time error in both cases.
2026, Paper I, Q4 (1 mark, MCQ) — A class needs to allow different implementations of a method. What concept to use?
- A) Inheritance
- B) Polymorphism
- C) Encapsulation
- D) None of these
Correct answer: B. Polymorphism (achieved via overriding) allows the same method name/signature to have different implementations across classes, with the correct one running based on the actual object type.
📜 Past-Paper Questions & MCQs (CSS 2016–2026)
2021 · Paper-I · Q6(b) and 2024 · Paper-I · Q5(b) · 6 marks each
Q. Differentiate between overloading and overriding with an example.
Ans.
| Basis | Overloading | Overriding |
|---|---|---|
| Meaning | Same function name with different parameters | A derived class redefines a base-class function with the same signature |
| Where | Within the same class (or scope) | Between base and derived classes (inheritance) |
| Binding | Compile time (static) | Run time (dynamic), needs virtual |
| Polymorphism type | Compile-time | Run-time |
class Calc { // overloading
public:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
};
class Animal { public: virtual void sound() { cout << "..."; } };
class Dog : public Animal { public: void sound() override { cout << "Bark"; } }; // overriding
2026 · Paper-I · Q4(c) · 8 marks
Q. Demonstrate method overriding using a base class Account and a derived class CurrentAccount. Make balance a protected data member.
Ans.
#include <iostream>
using namespace std;
class Account {
protected:
double balance; // visible to derived classes
public:
Account(double b = 0) : balance(b) {}
virtual void withdraw(double amt) { // can be overridden
if (amt <= balance) balance -= amt;
else cout << "Insufficient balance\n";
}
void show() { cout << "Balance: " << balance << endl; }
};
class CurrentAccount : public Account {
double overdraftLimit;
public:
CurrentAccount(double b, double limit) : Account(b), overdraftLimit(limit) {}
void withdraw(double amt) override { // overriding
if (amt <= balance + overdraftLimit) balance -= amt; // allows overdraft
else cout << "Overdraft limit exceeded\n";
}
};
int main() {
Account *a = new CurrentAccount(1000, 500);
a->withdraw(1300); // calls CurrentAccount::withdraw
a->show(); // Balance: -300
delete a;
}
- Key points:
protectedlets the derived class usebalance.virtualmakes the call resolve at run time toCurrentAccount::withdraw.
MCQs
2026 P-II · Q1. Which of the following statements is true about overloading and overriding?
- (A) Overloading and overriding are the same
- (B) Overloading and overriding are different, but inheritance is not involved in implementing any concept
- (C) Overloading is performed within the same class, but overriding is performed among different classes ✅
- (D) None of these
- Answer: (C).
- Why: overloading means several functions with the same name and different parameters in one class (resolved at compile time). Overriding means a derived class redefines a base-class function with the same signature, so it needs inheritance. (A) and (B) are therefore wrong.