Skip to content

Labs

Two tracks, one purpose: to make you write, break and redesign real C++ programs rather than read about them.

Track What it is Where it lives
Sequential module labs (1–16) One lab per module, extending a single running system from a procedural baseline to a designed application Summarised below; the full brief is each module's Lab page
Practice labs (1–32) Thirty-two standalone scenarios organised by module, each a complete lab sheet with requirements, tests, a debugging challenge and a completion checklist The practice labs below

Do the sequential labs in order — they are the spine of the course. The practice labs are for the week you need more of one idea: they share the same difficulty labels and the same self-assessment discipline, and they are independent of each other, so any of them can be done on its own.


The system you will build

A campus library lending system: items that can be lent, members who borrow them, loans with due dates, a catalogue, and rules about availability and limits.

It was chosen because every concept in this course has an honest reason to appear in it:

Concept Why the system needs it
Classes and objects An item and a member are things, not bags of fields
Constructors and invariants An item without an identifier is not an item
Encapsulation "Available" and "on loan" must never contradict each other
Operator overloading A catalogue must be sorted, searched and printed
Inheritance Several kinds of item share identifier, title and availability
Polymorphism Lending rules differ per kind of item
Composition A library has items and members; it is not a kind of item
Copy and move semantics A catalogue owns its storage, and copying it must not double-free
RAII and smart pointers Whoever owns an item must release it exactly once
Templates One repository type, several kinds of record
Exceptions An unavailable item is a failure the caller must handle
The STL Lookups, sorting and queries are library algorithms, not hand-written loops
Design patterns Lending policy and notifications must be replaceable

How the labs work

Lab 1    the procedural baseline       ← deliberately clumsy, on purpose
  ↓ extend
Lab 2    first classes
  ↓
  ...                                   ← one increment per module
  ↓
Lab 16   the capstone: designed, persistent, tested

Do the sequential labs in order. Each assumes the previous one is finished. Skipping one does not save time; it means the next lab starts from a program you do not have.

Attempt before comparing — and check whether a solution is published. Reference solutions for Labs 1 to 4 are in examples/labs/lab-01/solution/ through lab-04/solution/. From Lab 5 onward no solution file is published, on purpose: each of those labs is an increment on the program you already wrote, so the decisions are the exercise. Every lab page says what to compare instead — usually a named program under examples/module-NN/. The comparison is where the learning happens, especially where your version differs.

Working code is the minimum, not the goal. For each lab you should be able to answer why you structured it that way. Several labs ask for exactly that in writing, because "it compiles" is not a design.


The sequential labs

Lab Module What you build What it teaches you to feel
1 1 A menu-driven program storing item records in parallel global arrays with free functions, plus a written reflection on what hurt Why global state and long parameter lists do not scale
2 2 Book and Member as types with their own operations, in a header and a source file Data and behaviour belong together
3 3 Lifecycle counters on every class; you write down the predicted output before running Constructors and destructors happen more often than you think
4 4 Fully encapsulated types with enforced invariants, and a written justification of every public member An invariant enforced in one place cannot be broken from outside
5 5 Comparison, ordering and output operators; the catalogue becomes sortable, searchable and printable Making a type speak the language the standard library understands
6 6 A base type for lendable items and several derived kinds, with shared behaviour factored upward; then a diamond, deliberately, and its fix What is genuinely shared, and what is not
7 7 Virtual operations per item kind; a polymorphic container; object slicing reproduced and then fixed Why dispatch must go through a reference or pointer
8 8 The item type becomes an abstract interface; per-kind policies with a contract An interface is a promise the compiler enforces
9 9 Library owning items and members, Loan aggregating them, with an ownership diagram Who is responsible for what existing
10 10 An owning catalogue: reproduce the double-free, apply the Rule of Five, then reach the Rule of Zero; copy and move counters throughout Why the compiler's defaults are sometimes catastrophically wrong
11 11 All ownership converted to smart pointers, plus an RAII guard, with evidence of cleanup on both the normal and the exception path Cleanup should be automatic, not remembered
12 12 A generic Repository<T> used for two different record kinds Writing the container once instead of three times
13 13 A custom exception hierarchy, validated operations, and proof that nothing leaks on either path Failure is part of the design, not a special case
14 14 Queries rewritten as standard algorithms with lambdas, plus an indexed lookup container and a written complexity justification Justifying a container choice with evidence instead of habit
15 15 A factory for item creation, a strategy for lending policy, an observer for notifications, documented with UML Structure that survives the next feature
16 16 The complete application: design diagram, patterns, persistence, multi-file layout, tests, design report What "finished" actually means

The full brief for each lab is on the module page it belongs to: its scenario, what you start from, its requirements, what to deliver, a self-assessment checklist, and the ways that lab commonly goes wrong. Reference solutions for Labs 1 to 4 live in examples/labs/; the later labs publish none, for the reason each lab page gives.


The practice labs

Thirty-two complete lab sheets, organised by module and labelled by difficulty — two for each module, except Module 15, which gets three because the design work needs more than one scenario to land, and Module 16, which gets one long one and leaves the rest of the time for a capstone project. Each one states its scenario, requirements, constraints, test cases, expected behaviour, an extension, a debugging challenge, reflection questions and a completion checklist.

# Module Lab Difficulty Time
1 1 Student profile records, without classes Beginner 2–3 h
2 1 Library catalogue in plain structs Beginner 3 h
3 2 Rectangle and geometry model Beginner 2–3 h
4 2 Bank account as a type Beginner 3–4 h
5 3 Employee record with a lifecycle trace Beginner 3 h
6 3 Sensor register: constructors and temporaries Intermediate 4 h
7 4 Product catalogue with enforced invariants Beginner 3 h
8 4 Appointment slot with validated booking Intermediate 4 h
9 5 Money type with full operator support Intermediate 4–5 h
10 5 Warehouse bin with class-level barcodes Intermediate 4 h
11 6 Vehicle hierarchy Intermediate 4–5 h
12 6 Employee payroll hierarchy Intermediate 5 h
13 7 Shape drawing system Intermediate 4–5 h
14 7 Boarding queue: slicing reproduced and fixed Intermediate 4 h
15 8 Payment processing abstraction Advanced 5–6 h
16 8 Media player interface with contract tests Advanced 5 h
17 9 Library lending ownership model Intermediate 5 h
18 9 University course registration Intermediate 5–6 h
19 10 Dynamic collection: Rule of Three to Rule of Five Advanced 6 h
20 10 Invoice copy semantics Advanced 5 h
21 11 Smart pointer object manager Advanced 6 h
22 11 Device session guard with custom deleters Advanced 5–6 h
23 12 Generic repository for two record kinds Advanced 6 h
24 12 Ring buffer with non-type parameters Advanced 5 h
25 13 Order validation with an exception hierarchy Advanced 5–6 h
26 13 Failure-safe import with the strong guarantee Advanced 6 h
27 14 Inventory lookups, measured Intermediate 5 h
28 14 Notification framework from containers and callables Advanced 6 h
29 15 A plugin registry behind one interface Advanced 6 h
30 15 Refactoring a conditional into a design Expert 7 h
31 15 A design-pattern application, assembled Expert 8 h
32 16 Multi-module management system Expert 12–16 h

What the four difficulty labels mean

Label You are expected to The lab gives you
Beginner Write and modify a small program from a clear specification The type names and the shapes of the operations
Intermediate Choose your data members and operations, and get the invariants right Requirements and constraints; the design is yours
Advanced Design the classes, the ownership model and the failure behaviour Only the requirements, the constraints and the behaviour that is expected
Expert Make and defend design decisions, and evaluate what you chose Requirements, constraints, an evaluation checklist, and no design at all

The labels are about design freedom, not about size. An Expert lab is not longer than an Advanced one; it gives you less.


How to check your own work

There is no marker in self-study, so here is the checklist to run yourself. It is deliberately strict: passing it means you would pass a lab assessment.

It builds and runs

  • [ ] Compiles with g++ -std=c++17 -Wall -Wextra -Werror with no warnings
  • [ ] Runs and produces the behaviour the lab asked for
  • [ ] Every test case in the lab's own table was run, and the result recorded

It is correct

  • [ ] Invariants hold — try to break your own class from outside and fail
  • [ ] Boundary cases work: empty collection, one element, duplicates, the maximum
  • [ ] Failure cases are handled, not ignored

It is designed

  • [ ] You can state, in one sentence each, the responsibility of every class
  • [ ] Nothing outside a class pokes at its internal data
  • [ ] Ownership is explicit: you can say who owns every resource
  • [ ] You can explain why each relationship is composition, aggregation or plain association

It is verified

  • [ ] You predicted the output before running it, wherever the lab asks for a prediction
  • [ ] You tested the failure path, not only the success path
  • [ ] The debugging challenge was reproduced, explained and fixed — not merely fixed

It is explained

  • [ ] Where the lab asks for a written justification, a diagram or a count, it is actually written, and specific

Using reference solutions honestly

Reference solutions for the sequential labs are published so you can learn from them. Reading one before you have attempted the lab destroys its value: you will recognise the solution later and mistake that for understanding.

The honest sequence is:

  1. Attempt the lab from the brief alone.
  2. Get it building and running, however inelegantly.
  3. Then read the reference solution.
  4. Note the differences and decide which is better, and why.

If you are completely stuck after a genuine attempt, read the reference briefly, close it, and rewrite your own version from your own understanding. That is learning. Copying is not.

The practice labs have no published solutions, deliberately. Their feedback is the test-case table, the expected behaviour and the completion checklist — which is what you would have in a real job, where nobody hands you the answer to compare against.


Related: Exercises · Projects and capstone · Module index · Cheat sheets