Bank 5 — Inheritance¶
Twelve exercises on "is-a", on the order in which derived objects are built and torn down, and on the traps of the mechanism. Module 6. Levels L1–L6.
Solutions are in Solutions and answers; nudges are in Hints. Attempt first.
Exercises¶
E47 — Is-a, or just shares a field? L1 concept¶
A programmer makes Employee inherit from DateOfBirth because every employee has one.
Task. State the test that decides whether one type should inherit from another, and apply it to this case: does the design pass or fail, and what should the relationship be instead? Give two more examples of a tempting-but-wrong inheritance and the correct relationship for each.
E48 — Built like a stack, torn down like one L2 predict¶
#include <iostream>
class Base {
public:
Base() { std::cout << "Base "; }
~Base() { std::cout << "~Base "; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived "; }
~Derived() { std::cout << "~Derived "; }
};
int main() {
Derived d;
}
Task. Write the exact output, in order. State the two rules this shows: one for construction and one for destruction.
E49 — Pass the argument up L2 complete¶
#include <iostream>
class Shape {
public:
explicit Shape(int sides) : sides_(sides) {}
int sides() const { return sides_; }
protected:
int sides_;
};
class Polygon : public Shape {
public:
// TODO: a constructor taking (int sides, bool regular) that initialises the
// Shape part with `sides` and stores `regular`
bool regular() const { return regular_; }
private:
bool regular_;
};
int main() {
Polygon p{5, true};
std::cout << p.sides() << ' ' << p.regular() << '\n';
}
Task. Write the constructor so the program prints 5 1. Why must the Shape part
be built in the initialiser list rather than assigned in the body, while regular_
could be either?
E50 — Payroll L2 trace scenario¶
A payroll tool models salaried and hourly staff. Both share a name and an id.
#include <iostream>
#include <string>
class Employee {
public:
Employee(std::string name, int id) : name_(name), id_(id) {
std::cout << "hire " << name_ << '\n';
}
~Employee() { std::cout << "leave " << name_ << '\n'; }
protected:
std::string name_;
int id_;
};
class Manager : public Employee {
public:
Manager(std::string name, int id, int reports)
: Employee(name, id), reports_(reports) {
std::cout << "manager " << name_ << '\n';
}
~Manager() { std::cout << "manager leaves\n"; }
private:
int reports_;
};
int main() {
Manager m{ "Sana", 42, 6 };
}
Task. Write the four output lines in order. Then say which class's destructor body runs first and why.
E51 — Pull the shared part up L3 modify scenario¶
Two record types duplicate their identity fields.
#include <iostream>
#include <string>
class Book {
public:
Book(std::string id, std::string title)
: id_(id), title_(title) {}
std::string id() const { return id_; }
std::string title() const { return title_; }
private:
std::string id_;
std::string title_;
};
class Movie {
public:
Movie(std::string id, std::string title, int minutes)
: id_(id), title_(title), minutes_(minutes) {}
std::string id() const { return id_; }
std::string title() const { return title_; }
int minutes() const { return minutes_; }
private:
std::string id_;
std::string title_;
int minutes_;
};
int main() {
Book b{ "B1", "Dune" };
Movie m{ "M1", "Arrival", 116 };
std::cout << b.title() << ' ' << m.title() << ' ' << m.minutes() << '\n';
}
Task. Factor the shared id and title into a base class and have both derive from
it. Keep the program's output identical. Write down which access specifier the shared
members need, and why protected is a decision rather than a default.
E52 — Hidden, not overridden L3 debug¶
#include <iostream>
struct Base {
void describe() const { std::cout << "a shape\n"; }
};
struct Square : Base {
void describe(int sides) const {
std::cout << sides << " sides\n";
}
};
int main() {
Square s;
s.describe(); // line that will not compile
}
Task. Explain why s.describe() does not call Base::describe, name the rule
involved, and give two ways to make both calls possible. (This is name hiding, not
the virtual dispatch of Module 7 — do not reach for virtual yet.)
E53 — Which base constructor? L2 predict¶
#include <iostream>
class Base {
public:
explicit Base(int n) { std::cout << "Base(" << n << ") "; }
};
class Mid : public Base {
public:
explicit Mid(int n) : Base(n * 2) { std::cout << "Mid(" << n << ") "; }
};
class Leaf : public Mid {
public:
Leaf() : Mid(5) { std::cout << "Leaf "; }
};
int main() {
Leaf l;
}
Task. Write the output. Then: if Line had no base initialiser at all, what would
the compiler require of Base, and why?
E54 — The diamond L4 analyze¶
struct Device {
int id;
};
struct Networked : Device { };
struct Powered : Device { };
struct Router : Networked, Powered { };
int main() {
Router r;
// r.id = 7; // (1) does this compile?
r.Networked::id = 1; // (2) and this?
}
Task. (a) Why does line (1) not compile? (b) How many Device sub-objects does a
Router contain, and how do you know? (c) Line (2) compiles — what does that tell you
about the "diamond" here? (d) Name the C++ feature from a later module that a real
diamond-shaped hierarchy would need to share a single base, and what it costs.
E55 — Inaccessible, and why L3 debug¶
#include <iostream>
class Engine {
public:
void start() const { std::cout << "started\n"; }
int power() const { return 120; }
};
class Car : Engine { // note: no access specifier
public:
Car() { start(); }
};
int main() {
Car c;
// c.power(); // will this compile?
}
Task. Car uses Engine's public members internally just fine, but main cannot
call c.power(). Explain why, name the access specifier class implies by default,
and state the one-character-class change that fixes it. When would keeping the
inheritance private be the right choice on purpose?
E56 — Two sub-objects, two destructors L4 predict¶
#include <iostream>
class Part {
public:
explicit Part(char id) : id_(id) { std::cout << "make " << id_ << ' '; }
~Part() { std::cout << "drop " << id_ << ' '; }
private:
char id_;
};
class Assembly : public Part {
public:
Assembly() : Part('A'), small_('B') { std::cout << "assemble "; }
~Assembly() { std::cout << "disassemble "; }
private:
Part small_;
};
int main() {
Assembly a;
}
Task. Write the output in order. Pay attention to when small_ is constructed
relative to the Part base — the rule is the same as E48's, extended to members.
E57 — Design a vehicle fleet L5 design inherit scenario¶
A vehicle-rental desk tracks cars, vans and motorbikes. All vehicles have a registration plate and a daily rate. Cars carry a number of seats; vans carry a payload in kg; motorbikes carry an engine size in cc. The desk needs one list of "all vehicles" and a per-day cost per vehicle.
Task. Design the hierarchy. Give:
- the base type's name, which members are shared, and what it promises;
- each derived type and the data it adds;
- where the daily cost lives, given that each kind might price differently later;
- one thing that is not shared and therefore must not go in the base, and why putting it there would be a mistake.
How you will be judged. On whether the base holds only what is genuinely common, and on whether you resisted a base member that fits one kind and not the others.
E58 — Three classes, one shape L6 refactor scenario¶
Three unrelated classes each maintain a createdOn date, an owner name, and a
bool active flag, plus the same four-line deactivate() method copied three times.
A bug was fixed in one copy and not the others.
Task. Write a refactoring plan that removes the duplication. It must:
- decide whether the shared part is a base class or a has-a member, and defend the choice — this is the crux of the exercise;
- say what happens to the three existing
deactivate()methods; - name one place where the copy-paste was actually hiding a difference that must be preserved;
- state how you would check the refactor changed nothing observable.
Success. You picked a relationship deliberately, and you tested the refactor rather than trusting it.
Previous topic: Operators and static members · Next topic: Polymorphism and interfaces · Hints: hints.md · Solutions: solutions.md