optional · CSS study notes
8. Inheritance
- Single inheritance: one derived class from one base class
- Multiple inheritance: one derived class from more than one base class (C++ supports this; some languages like Java do not, using interfaces instead)
- Access levels in inheritance:
public(preserves accessibility),protected(accessible within class and derived classes, not outside),private(accessible only within the defining class)
class Animal { public: void eat() { cout << "eats\n"; } };
class Dog : public Animal { public: void bark() { cout << "barks\n"; } };
// Dog inherits eat() from Animal, adds bark() of its own
MCQ Trap: C++ supports multiple inheritance; many other OOP languages (e.g., Java) restrict classes to single inheritance and use interfaces instead.
What a derived class actually inherits
A derived class gets access to all public and protected members of its base class (not private — those stay locked to the base class itself, even for derived classes). It can then add new members and override existing behavior.
class Animal
{
protected:
string name;
public:
Animal(string n) : name(n) {}
void eat() { cout << name << " eats\n"; }
};
class Dog : public Animal
{
public:
Dog(string n) : Animal(n) {} // must call base constructor explicitly
void bark() { cout << name << " barks\n"; } // name accessible -- it's protected, not private
};
int main()
{
Dog d("Rex");
d.eat(); // inherited from Animal
d.bark(); // Dog's own method
return 0;
}
Constructor call order — base runs FIRST, always
When a Dog object is created, Animal's constructor runs before Dog's own constructor body executes. Destructors run in the opposite order (derived first, then base) — a common exam trap.
class A { public: A() { cout << "A constructed\n"; } ~A() { cout << "A destroyed\n"; } };
class B : public A { public: B() { cout << "B constructed\n"; } ~B() { cout << "B destroyed\n"; } };
// B b; --> Output: "A constructed", "B constructed", then on scope exit: "B destroyed", "A destroyed"
Inheritance access-level table (base access → resulting access in derived class)
| Base member | public inheritance | protected inheritance | private inheritance |
|---|---|---|---|
public | stays public | becomes protected | becomes private |
protected | stays protected | stays protected | becomes private |
private | not accessible at all | not accessible at all | not accessible at all |
public inheritance (class Dog : public Animal) is by far the most common — it preserves the "is-a" relationship cleanly.
Important MCQs
- Which access specifier is NOT inherited/accessible at all in a derived class, regardless of inheritance type?
- a) public b) protected c) private d) static
- When a derived-class object is created, which constructor runs first?
- a) Derived class's b) Base class's c) Both simultaneously d) Neither
- Destructors, in an inheritance chain, run:
- a) In the same order as constructors b) In the reverse order of constructors (derived first, then base) c) Randomly d) Never automatically
- C++ supports multiple inheritance; which popular OOP language restricts classes to single inheritance and uses interfaces instead?
- a) Python b) Java c) C++ d) PHP
✅ Self-Test Questions (with Answers)
Q1. Why must a derived class's constructor explicitly call the base class's constructor?
Ans: Strictly, the base constructor is always called — the question is whether you choose which one. Every derived object physically contains a base sub-object, which must be fully constructed before the derived constructor body runs. If the base has an accessible default constructor, the compiler inserts a call to it silently and you need write nothing. But if the base has only parameterised constructors — or if you need to pass initialisation data up to it — there is no default to fall back on, so you must name it explicitly in the member initializer list. This is also the only place it can be done: by the time the constructor body opens, the base sub-object already exists, and a body statement could only assign to it, not initialise it.
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) { } // NO default constructor exists
};
class Dog : public Animal {
public:
Dog(string n) : Animal(n) { } // CORRECT: base initialised in the init list
// Dog(string n) { name = n; }
// ERROR: no matching function for call to 'Animal::Animal()'
// -- the compiler tried to insert Animal() implicitly and found none
};
int main() {
Dog d("Rex");
d.eat(); // Rex eats
}
The reasons to state in an exam answer:
- The base sub-object must be in a valid state before any derived code can use inherited members.
- Base-class private members are invisible to the derived class, so only the base constructor can initialise them — there is literally no other route.
constmembers and reference members of the base can only ever be initialised, never assigned.- Assigning in the body would mean double work: default-construct, then overwrite.
Traps: construction order is base → derived regardless of where the base call appears in your initializer list, and destruction is the reverse. In multiple inheritance, bases are constructed in the order they are listed in the class declaration, not the order you write them in the list. And since C++11, using Base::Base; lets a derived class inherit the base's constructors outright, removing the boilerplate.
Q2. In what order do constructors and destructors run in an inheritance chain — and why does the order flip for destructors?
Ans: Constructors run base → derived (most-base first, most-derived last); destructors run derived → base, the exact reverse. The reason is dependency. A derived class is built on top of its base: its constructor body may call inherited functions and use inherited data, so the base sub-object must already be fully constructed before the derived constructor body begins. Destruction reverses this because the derived part may still need its base during its own cleanup — destroying the foundation before the upper storey would leave the derived destructor operating on dead members. The mental model is a building: foundation first when constructing, top floor first when demolishing.
class A { public: A() { cout << "A ctor\n"; } virtual ~A() { cout << "A dtor\n"; } };
class B : public A { public: B() { cout << "B ctor\n"; } ~B() { cout << "B dtor\n"; } };
class C : public B { public: C() { cout << "C ctor\n"; } ~C() { cout << "C dtor\n"; } };
int main() { C obj; }
// Output:
// A ctor
// B ctor
// C ctor <-- construction: base to derived
// C dtor
// B dtor
// A dtor <-- destruction : derived to base
Full construction order inside a single class (worth stating for extra marks): (1) virtual base classes, (2) direct base classes in declaration order, (3) non-static data members in the order they are declared in the class — not the order written in the initializer list — and finally (4) the constructor body. Destruction reverses all four steps.
The critical trap: this automatic reverse chaining only happens correctly when deleting through a base pointer if the base destructor is virtual. With a non-virtual base destructor, delete basePtr; on a derived object calls only ~A(), skipping ~C() and ~B() — undefined behaviour, and in practice a resource leak. Hence the rule: any class intended as a polymorphic base must have a virtual (or protected) destructor. Note also that virtual dispatch is effectively disabled during construction and destruction: calling a virtual function from a base constructor invokes the base version, because the derived part does not exist yet.
Q3. If Animal::name were private instead of protected, would Dog::bark() still compile? Why or why not?
Ans: No — it would fail to compile, with an error such as "'std::string Animal::name' is private within this context." This is the single most-tested distinction between private and protected. A private member of a base class is inherited but not accessible: the name sub-object physically exists inside every Dog and is counted in sizeof(Dog), but only Animal's own member functions and its declared friends may name it. A protected member, by contrast, is accessible to the class itself and to all its descendants, while remaining hidden from outside code.
class Animal {
private: // was: protected
string name;
public:
Animal(string n) : name(n) {}
string getName() const { return name; } // the sanctioned route in
void eat() { cout << name << " eats\n"; } // fine: Animal's own member
};
class Dog : public Animal {
public:
Dog(string n) : Animal(n) {}
void bark() {
// cout << name << " barks\n"; // ERROR: 'name' is private within this context
cout << getName() << " barks\n"; // OK: goes through the public accessor
}
};
The two ways to make it compile again: change name to protected (direct access for every subclass), or keep it private and reach it through the public/protected accessor getName(). The second is better design: private plus accessors keeps the base class free to change its internal representation later, whereas protected data effectively becomes part of the public contract with every present and future subclass — which is why the usual advice is "protected member functions, private data."
MCQ traps: private members are inherited (they occupy space in the derived object); they are simply not accessible, so "not inherited" is the wrong phrasing. And the inheritance mode makes no difference here — base-class private members are inaccessible under public, protected and private inheritance alike.
📌 Sample & Repeated FPSC Questions (2016–2026)
2026, Paper I, Q4(a) (6 marks) — Why cannot we instantiate an abstract class directly, even though it may contain complete functions?
A class is abstract the moment it declares at least one pure virtual function (= 0) — regardless of how many other fully-implemented functions it also has. That one unimplemented function means the class's interface is deliberately incomplete: if an object of it could be created and that pure virtual function called, there would be no function body to execute — undefined behavior. So the compiler makes it a compile-time error to instantiate any class with an un-overridden pure virtual function. It becomes usable only indirectly, once a derived class supplies a concrete override for every pure virtual function — at which point that derived class becomes instantiable.
Key reasoning points:
- "Complete functions elsewhere" doesn't help — even ONE pure virtual function is enough to block instantiation.
- The class is only "brought to life" through a derived class implementing all pure virtual members.
- This is exactly how abstract classes enforce a common interface/contract while still sharing complete helper functions.
2026, Paper I, Q4(b) (6 marks) — A class library contains many book objects. Should this be modeled using inheritance or composition? Why?
Composition, not inheritance. Inheritance models "is-a" (a NovelBook is-a Book), but a Library is not a kind of Book — it has many books. That's a "has-a" relationship, which composition represents directly: Library holds a collection of Book objects as member data.
class Library
{
vector<Book> books; // composition: Library HAS-A collection of Books
public:
void addBook(const Book& b) { books.push_back(b); }
};
Key reasoning points:
- Inheritance is a single-parent, "is-a" relationship for one conceptual type — wrong fit for "owns many of."
- Composition captures both ownership and the one-to-many nature correctly.
- Common examiner trap: students default to inheritance for any class-relates-to-class question; always ask "is-a" vs "has-a" first.
(For a fuller worked example of composition — object creation/destruction lifetime tied to an owning class — see the "Classes and Objects — Exam Practice" page's composition examples.)
📜 Past-Paper Questions & MCQs (CSS 2016–2026)
2018 · Paper-I · Q4(d) · 5 marks
Q. For the classes Car, SteeringWheel, Vehicle, Van, Minivan, AudioSystem and ParkingLot, describe all is-a and has-a relationships, include an inheritance hierarchy, and fill in the table.
Ans.
- Inheritance hierarchy: Vehicle → Car; Vehicle → Van → Minivan.
- Is-a: Car is-a Vehicle. Van is-a Vehicle. Minivan is-a Van (and so also a Vehicle).
- Has-a: Car has-a SteeringWheel. Car has-a AudioSystem. ParkingLot has-a Vehicle (it holds vehicles).
| Row ... column | Vehicle | Car | Van | Mini Van | Steering Wheel | Audio System | Parking Lot |
|---|---|---|---|---|---|---|---|
| Vehicle | |||||||
| Car | is-a | has-a | has-a | ||||
| Van | is-a | ||||||
| Mini Van | is-a (through Van) | is-a | |||||
| Steering Wheel | |||||||
| Audio System | |||||||
| Parking Lot | has-a |
- The table is read as "row is-a / has-a column". Cells with no relation stay empty.
2024 · Paper-I · Q5(c) · 8 marks
Q. How do you prevent a class from being extended? How do you prevent a method from being overridden? Exemplify with a simple piece of code.
Ans.
- Prevent extension: mark the class
final(C++11 and later, and Java). Any attempt to derive from it is a compile error. - Prevent overriding: mark the virtual method
final.
class Base final { }; // cannot be inherited
// class Derived : public Base { }; // error
class Shape {
public:
virtual void draw() final { } // cannot be overridden
};
final class Utility { } // no subclass allowed
class Account {
public final void audit() { } // subclasses cannot override
}
- Why: to protect security-critical or fixed behaviour and to keep a design stable.