Skip to content

Practice Lab 14 — Boarding queue: slicing reproduced and fixed

Difficulty: Intermediate · Estimated time: 4 hours · Module 7: Polymorphism and Virtual Functions

Object slicing is the failure that produces no error, no warning, and no crash — just a lost field. This lab makes you watch it happen and then remove the possibility.


At a glance

Difficulty Intermediate
Estimated time 4 hours
Module Module 7 — Polymorphism and Virtual Functions
Concepts practised Base-class pointers and references, object slicing, copying polymorphic objects, containers of base values versus base pointers, override, virtual destructors
Prerequisite Practice Lab 13
Class design yours — with the slicing failure specified

Scenario

An airport boarding desk holds a queue of passengers. Every passenger has a name, a seat and a boarding group. Some have extras: a wheelchair requirement, a connecting flight, or a pet in the cabin — and those extras change the order in which they should board, and what the announcement says.

The desk's current code stores passengers in a vector of the base type, and it has spent a fortnight chasing a bug it described as "the wheelchair flag disappears after the passenger joins the queue". That description is a precise diagnosis of a specific C++ failure, and this lab is about it.


Problem statement

Implement:

  1. A Passenger base type with a name, a seat, a boarding group, and a virtual boarding_note().
  2. Three derived types carrying an extra field each, each overriding boarding_note() and adding an operation that uses its extra field.
  3. A boarding queue with operations to add a passenger, print the queue in boarding order, print the announcements, and count passengers by boarding group.
  4. A deliberate slicing demonstration: a function that takes Passenger by value and prints the note, called with a derived passenger. Record what is printed and which fields are lost.
  5. The fixed version: the queue stores passengers in a way that preserves their type, with a documented ownership rule and a virtual destructor.
  6. A copy demonstration: what happens when a derived passenger is copied into a base variable, and what the copy does with the derived fields.

In slicing-notes.md:

  • The exact output of the sliced call, and the two fields it lost.
  • Which of these four has the failure: a base variable assigned a derived object, a function taking the base by value, a container of base values, a container of base pointers. Answer each yes/no with a one-line reason.
  • The rule you will follow from now on, in one sentence you could give a colleague.

Learning objectives

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

  • Reproduce object slicing and explain it as "a copy of the base part only".
  • Recognise each of the four places slicing can occur, and avoid them by design.
  • Store polymorphic objects in a container with a correct ownership and destruction rule.
  • Explain why a base class with virtual operations must not be passed by value, in terms of what is copied.
  • Choose between owning pointers and references for a container, and state the cost of the choice.

Functional requirements

  1. Every passenger has a name, a seat and a boarding group; a boarding group outside 1..4 is refused.
  2. Boarding order is by group, then by a documented tie-break rule; the order is stable for equal keys.
  3. Each derived type's extra field is used: by the note, by the order, or by a query. A derived type whose extra field changes nothing is a design smell — say so if you find one.
  4. The announcements are one line per passenger, printed in boarding order.
  5. Counting by group is a single pass, and the counts sum to the queue size.
  6. The sliced call is present, marked in the source with a comment saying it is deliberately wrong, and its output is recorded in the notes.
  7. The queue's stored passengers are destroyed exactly once, and destroying them narrates the derived destructor.
  8. A passenger found by seat is returned without copying it.
  9. No dynamic_cast is needed anywhere; if you reach for one, record why.
  10. An empty queue prints a clear message for each report.

Constraints

  • Header per type family plus one source file; explicit build list.
  • The queue's storage is your choice, with the ownership stated in writing. The sliced version must exist somewhere in the program, clearly labelled, so the failure is reproducible.
  • No smart pointers (Module 11) — but you must state who deletes each passenger and prove the count.
  • No templates in the queue (Module 12 will rewrite it as one).
  • No warnings — which means the slicing case must not trip -Werror; if it does, record the warning and say why you kept the code.

Starter task

In slicing-notes.md, before coding:

  1. Predict the output of the by-value call with a wheelchair passenger. Write the exact lines you expect.
  2. Predict the output of the same call with a base reference instead.
  3. Choose your queue's storage, and write the sentence that says who owns the passengers.
  4. Predict what sizeof says for the base and one derived type, and by how much they differ. Then measure it with sizeof and record both numbers.

Implementation tasks

  1. Implement the base and the three derived types with narration.
  2. Implement the queue with the ownership rule.
  3. Implement the reports and the boarding order.
  4. Implement the sliced call and record its output.
  5. Implement the fixed call, and print both side by side in main.
  6. Destroy the queue, count the narrations, and record that the count matches the number added.

Test cases

# Action Expected result
1 Add a plain passenger, print One line, base note
2 Add a wheelchair passenger, print Derived note, mentioning the extra field
3 Print in boarding order with mixed groups Sorted by group; tie-break as documented
4 Count by group Counts sum to the queue size
5 Call the by-value function with a wheelchair passenger The base note; the extra field is gone — output recorded
6 Call the reference version with the same passenger The derived note
7 sizeof(Passenger) versus sizeof(WheelchairPassenger) Two numbers, recorded
8 Clear the queue One derived narration per passenger; count matches
9 Find a passenger by seat Returns without copying; no narration
10 Boarding group 5 at construction Refused
11 Empty queue reports Clear messages, no crash
12 Add a passenger and then add a copy Two passengers, both destroyed once — or the copy is refused: your rule, stated

Boundary cases to run deliberately: two passengers in the same group and the same seat letter; a queue of one; a derived passenger whose extra field is empty (for example, no connecting flight details).


Expected behaviour

boarding order:
  group 1  Ayesha Khan   seat 1A   note: boarding with wheelchair assistance
  group 2  Bilal Ahmed   seat 7C   note: connecting flight at 18:40
  group 3  Zainab Iqbal  seat 22F  note: travelling with a pet
sliced call with a wheelchair passenger:
  note: boarding
  (the wheelchair requirement was sliced away)
by-reference call with the same passenger:
  note: boarding with wheelchair assistance
sizeof: Passenger 64, WheelchairPassenger 72
clearing the queue: 3 destructor narrations for 3 passengers

Extension challenge

Add a priority boarding rule that depends on a derived type's extra field — a passenger with a connecting flight boards in group 1 regardless of their seat group.

Record in slicing-notes.md:

  • Where the rule lives, and how it reads the extra field without a type check.
  • Whether the rule belongs on the passenger (the passenger knows why it should board early) or on the queue (the queue owns the policy). Choose one, and give the rejected option's cost.
  • What happens to the boarding order when two passengers both qualify, and whether your tie-break rule still holds.
  • The change cost: files, operations, lines.

Debugging challenge

The queue silently loses data, and the code has a comment claiming it does not:

class BoardingQueue {
public:
    void add(const Passenger& passenger) {
        passengers_.push_back(passenger);    // "stores a copy of the passenger"
    }
private:
    std::vector<Passenger> passengers_;      // a vector of VALUES
};

Reproduce it — add a wheelchair passenger, print the queue — and answer:

  1. What exactly did push_back copy, and what was lost?
  2. Why did the comment make the bug harder to find rather than easier?
  3. Which two changes fix it, and what does each one cost (an allocation, a lifetime, a destructor)?
  4. Which test case in the table catches it?

Reflection questions

  1. Where in your program is a derived object copied into a base variable, if anywhere? How did you check?
  2. Your queue stores pointers or references. What is the failure mode if a passenger is destroyed while still in the queue, and how would you prevent it?
  3. A base class with virtual operations should not be passed by value. Why is that a rule about the base, not about the derived types?
  4. Which of your derived types changed the boarding order, and which only changed the announcement? Is one of them a design smell?
  5. sizeof differed by eight bytes. What would happen if a derived class's extra field were much larger than the base, and the object were sliced into a base array?
  6. If a fifth kind of passenger arrived monthly, what would the change cost be in your design, and where would the extra field have to be handled?

Completion checklist

  • [ ] It builds from a documented command with -Wall -Wextra -Werror and no warnings
  • [ ] The sliced call exists, is labelled as deliberate, and its output is in the notes
  • [ ] The fixed call is shown beside it, and the difference is explained
  • [ ] Four slicing contexts answered yes/no with reasons
  • [ ] sizeof recorded for the base and a derived type
  • [ ] The queue's ownership rule is written down, and destruction narrations match the number added
  • [ ] No dynamic_cast — or the reason for one is recorded
  • [ ] All twelve test cases run, with output recorded
  • [ ] Boundary cases run, including a queue of one and a derived passenger with no extra data
  • [ ] The debugging challenge was explained before being fixed
  • [ ] I can state the one-sentence rule that prevents slicing in my own code

Related: Lab index · Module 7 — Polymorphism · Previous: Practice Lab 13 · Next: Practice Lab 15 — Payment processing abstraction