Skip to content

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:

  1. the base type's name, which members are shared, and what it promises;
  2. each derived type and the data it adds;
  3. where the daily cost lives, given that each kind might price differently later;
  4. 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:

  1. 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;
  2. say what happens to the three existing deactivate() methods;
  3. name one place where the copy-paste was actually hiding a difference that must be preserved;
  4. 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