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:
- 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.
- 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.
- 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.
- Constructor and destructor narration in every class, so the order of construction and destruction is visible and predictable.
- 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
protectedrather 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
protecteddeliberately, 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¶
- Every hired item has a unique identifier, a description and a positive daily rate; an invalid rate is refused.
- 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.
- Each derived type adds at least one member that the base does not have, and at least one operation that uses it.
- At least one operation is overridden in a derived type — without
virtualyet; you will observe what happens and record it. (Module 7 introducesvirtual; this lab makes you see the problem first.) - Trailers are stored in their own collection, and no operation on the vehicle collection accepts one.
- 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.
- Revenue totals are computed without
switchstatements over the type. - 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
virtualin 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.
protectedonly where a derived class genuinely needs access, and each use is justified inhierarchy.md.- No warnings.
Starter task¶
In hierarchy.md, before coding:
- 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.
- 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.
- 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.
- 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¶
- Implement the base type with the hire/return state and the narration.
- Implement the three vehicle types and the trailer type, each with its own members and operations.
- Implement the fleet printing and the revenue total.
- Write
mainexercising every test case, including the reference-based print from case 8. - Compare the narration with your prediction, and record every difference.
- Write the three
hierarchy.mdanswers 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:
- Which of the manager's requests does this code break, and where exactly?
- What is
vehicle.load_capacity_kg()doing in the base type, and what does its presence say about the design? - What would the right shape be: an overridden operation, a separate lookup, or something else? Answer
without writing
virtualyet, and then say whatvirtualwould give you. - Which test case in the table would have caught it?
Reflection questions¶
- Which member did you move up into the base, and which did you refuse to move up? What decided it?
- 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.
- Your narration showed construction and destruction orders. Which of those orders does the standard guarantee, and which is your compiler's choice?
- 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?
- 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?
- 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 -Werrorand 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
protectedmember is justified inhierarchy.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
switchover 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