Skip to content

Practice Lab 11 — Vehicle hierarchy

Difficulty: Intermediate · Estimated time: 4–5 hours · Module 6: Inheritance

Inheritance is for what a thing is. This lab puts that sentence under pressure, because a vehicle fleet contains three things that look like a hierarchy and one that only looks like one.


At a glance

Difficulty Intermediate
Estimated time 4–5 hours
Module Module 6 — Inheritance
Concepts practised Base and derived classes, constructor and destructor order, protected members, overriding without virtual, composition versus inheritance, refactoring shared members upward
Prerequisite Practice Lab 10
Class design the relationships are given; the members and operations are yours

Scenario

A hire company runs cars, vans and motorcycles, and also rents trailers — which are not vehicles at all: they have no engine, no seats and no driver, and are towed by something else.

The company's previous system made Trailer a kind of Vehicle, and every vehicle operation had to check whether it was really a trailer first. The manager's request for this rebuild is blunt: nothing in this system should have to ask what kind of thing it is.


Problem statement

Design and implement:

  1. A base type for things the company hires out, holding what is genuinely shared: an identifier, a description, a daily rate, and whether it is currently hired.
  2. Derived types for cars, vans and motorcycles, each adding what is specific to it — seats, load capacity, engine size, licence category, or whatever your requirements analysis says.
  3. A separate type for trailers, sharing only what is honestly shared with all hired items — and not derived from the vehicle base if that relationship would be a lie.
  4. Constructor and destructor narration in every class, so the order of construction and destruction is visible and predictable.
  5. An operation that prints every hired item in the fleet, and one that reports the total daily revenue if everything were hired.

In hierarchy.md, answer the three questions this lab exists for:

  • What is the one sentence that justifies each inheritance relationship you used?
  • Which type did you refuse to derive from another, and what would the wrong version have cost?
  • Which member is protected rather than private, and why does a derived class need it?

Learning objectives

By the end of this lab you will be able to:

  • Identify genuine is-a relationships and refuse relationships that are merely convenient.
  • Write base and derived constructors so that base state is initialised before derived state, and explain the order of destruction.
  • Use protected deliberately, and explain why private would have failed and public would have been worse.
  • Factor shared members upward without dragging unrelated members along.
  • Predict and then verify the constructor/destructor narration for a fleet of mixed types.

Functional requirements

  1. Every hired item has a unique identifier, a description and a positive daily rate; an invalid rate is refused.
  2. Hiring and returning change a state that is enforced in the base type, in one place: an item cannot be hired twice, and cannot be returned if it is not hired.
  3. Each derived type adds at least one member that the base does not have, and at least one operation that uses it.
  4. At least one operation is overridden in a derived type — without virtual yet; you will observe what happens and record it. (Module 7 introduces virtual; this lab makes you see the problem first.)
  5. Trailers are stored in their own collection, and no operation on the vehicle collection accepts one.
  6. Printing the fleet is a single function that does not ask what kind of item it is holding — inside the limits of point 4, and you must record exactly where those limits begin.
  7. Revenue totals are computed without switch statements over the type.
  8. Construction and destruction narrate, and the total narration count is accounted for in your notes.

Constraints

  • A header per type family plus a source file; explicit build list: g++ -std=c++17 -Wall -Wextra -Werror fleet.cpp main.cpp -o hire
  • No virtual in this lab — deliberately. Module 7 is where it arrives, and seeing the failure first is the point.
  • No templates, no smart pointers, no multiple inheritance.
  • protected only where a derived class genuinely needs access, and each use is justified in hierarchy.md.
  • No warnings.

Starter task

In hierarchy.md, before coding:

  1. Write the shared members you intend to put in the base, and for each one say which derived types use it. Move anything only one type uses out of the base.
  2. Write the is-a sentence for every inheritance relationship. If a sentence needs "sort of", the relationship is wrong; say what you will do instead.
  3. Write the constructor/destructor narration you expect for: a base, a derived, a derived inside a scope, and an array of derived objects. Predict the exact order.
  4. Say what you expect to happen when a base-type reference is used to print a derived object. This is the prediction that Module 7 will answer.

Implementation tasks

  1. Implement the base type with the hire/return state and the narration.
  2. Implement the three vehicle types and the trailer type, each with its own members and operations.
  3. Implement the fleet printing and the revenue total.
  4. Write main exercising every test case, including the reference-based print from case 8.
  5. Compare the narration with your prediction, and record every difference.
  6. Write the three hierarchy.md answers with real line references from your code.

Test cases

# Action Expected result
1 Construct a car with a rate of 0 Refused
2 Hire an item, hire it again Second hire refused
3 Return an item that is not hired Refused
4 Print a fleet of one car, one van, one motorcycle Three lines, each with the type's own detail
5 Total daily revenue with all three hired The sum of the three rates, exactly
6 Create a derived object in a nested scope Derived constructed after base; destructed before base
7 An array of three derived objects Three base narrations then three derived narrations; destruction in reverse
8 Print a derived object through a base reference The prediction from your starter task; record what actually happens
9 Add a trailer to the vehicle collection Does not compile; the message is recorded
10 Hire a trailer and print the trailer collection Works, with the trailer's own detail only

Boundary cases to run deliberately: a fleet with zero items; a single derived type in a collection of base objects (if the compiler permits it) and what happens to its extra members.


Expected behaviour

[base ctor] V-1  City car
[derived ctor] V-1  City car  4 seats
hire V-1 -> hired
hire V-1 -> refused: already hired
return V-1 -> returned
[derived dtor] V-1  City car
[base dtor] V-1  City car
fleet revenue if all hired: 12700 pence/day
trailer T-1 is stored with trailers, not with vehicles

Extension challenge

Add a fourth vehicle type — a minibus — and then add a maintenance record to every hired item (vehicle and trailer both).

Record in hierarchy.md:

  • How many files the minibus touched, and how many files the maintenance record touched. The second number is the interesting one: it is the count for a member that belongs in a shared place.
  • Whether the maintenance record went into a shared base, into both types separately, or into a composed object — and the cost of the choice you made.
  • What this tells you about the difference between being a kind of thing and having something.

Debugging challenge

The fleet total is wrong whenever a van is present:

int Fleet::total_daily_rate() const {
    int total = 0;
    for (const Vehicle& vehicle : vehicles_) {
        total += vehicle.daily_rate();          // the base's rate
        if (vehicle.load_capacity_kg() > 0) {   // "vans cost more per kilogram"
            total += vehicle.load_capacity_kg();
        }
    }
    return total;
}

Reproduce it — one van, and a car with a load capacity of zero — and answer:

  1. Which of the manager's requests does this code break, and where exactly?
  2. What is vehicle.load_capacity_kg() doing in the base type, and what does its presence say about the design?
  3. What would the right shape be: an overridden operation, a separate lookup, or something else? Answer without writing virtual yet, and then say what virtual would give you.
  4. Which test case in the table would have caught it?

Reflection questions

  1. Which member did you move up into the base, and which did you refuse to move up? What decided it?
  2. A protected member is accessible to every derived class. Name one member that should be private and say what a derived class would do to get the value it needs.
  3. Your narration showed construction and destruction orders. Which of those orders does the standard guarantee, and which is your compiler's choice?
  4. Test case 8 printed a derived object through a base reference. What happened, and what would a caller need before the right behaviour was possible?
  5. The trailer shares almost nothing with a vehicle. Which members, if any, did it still share, and would a common base for all hired items have been honest?
  6. If a new requirement said "vehicles can be towed by other vehicles", would inheritance still be the right tool? Answer in two sentences.

Completion checklist

  • [ ] It builds from a documented command with -Wall -Wextra -Werror and no warnings
  • [ ] Every inheritance relationship has an is-a sentence that does not need "sort of"
  • [ ] Every shared member is used by at least two derived types
  • [ ] Every protected member is justified in hierarchy.md
  • [ ] The constructor/destruction narration was predicted before running, and every difference is explained
  • [ ] Hiring and returning are enforced in one place in the base
  • [ ] No switch over the type anywhere — searched for and recorded
  • [ ] A trailer cannot be added to the vehicle collection, and the compile error is recorded
  • [ ] All ten test cases run, with output recorded
  • [ ] The debugging challenge was explained before being fixed

Related: Lab index · Module 6 — Inheritance · Previous: Practice Lab 10 · Next: Practice Lab 12 — Employee payroll hierarchy