Practice Lab 18 — University course registration¶
Difficulty: Intermediate · Estimated time: 5–6 hours · Module 9: Composition, Aggregation and Association
An aggregation-heavy model: the same student object appears in many places, and the question is whether anybody owns them.
At a glance¶
| Difficulty | Intermediate |
| Estimated time | 5–6 hours |
| Module | Module 9 — Composition, Aggregation and Association |
| Concepts practised | Aggregation versus composition, shared objects, many-to-many relationships, dependency direction, deleting safely, const access to shared objects |
| Prerequisite | Practice Lab 17 |
| Class design | yours — the relationships are the deliverable |
Scenario¶
A department registers students for courses. A student enrols in several courses; a course holds many students. Instructors teach sections, and a section belongs to one course. Rooms are booked by sections.
The registrar's old system kept copies of each student inside each course, and after a fee change half the department had the old fee recorded. That is the failure this lab is designed to prevent: shared data that gets copied is shared data that gets inconsistent.
Problem statement¶
Design and implement the department's registration model.
- Students, courses, sections, instructors and rooms, with the relationships the department describes.
- Many-to-many enrolment: a student in several sections, a section with many students — with no copy of a student stored anywhere.
- A capacity limit per section, refused when exceeded.
- An enrolment type that records when a student joined a section and their current state.
- Reports: a student's timetable, a section's roster, a room's bookings for a week, and a course's total enrolment.
- A fee per student, computed once from their programme, and demonstrably the same value wherever it is read.
- Narration of construction and destruction in every class.
Deliverables in relationships.md:
- A diagram of the model with the relationship kinds and multiplicities, including the many-to-many.
- For each relationship: owned, shared, or merely referenced — and the code that says so.
- The answer to this question: if a student leaves, what is destroyed, and what is only unlinked? with the test that proves it.
Learning objectives¶
By the end of this lab you will be able to:
- Model a many-to-many relationship without copying either side.
- Decide whether an object is owned, shared or referenced, and express it in the member's type.
- Delete or unlink safely when an object leaves the system, and prove nothing dangles.
- Read a value through several paths and show it is one value, not several copies.
- Say what a capacity limit is a property of, and enforce it in the right place.
Functional requirements¶
- A student appears in every report with the same fee value, read through different paths — demonstrable by changing it once and printing every path.
- No class stores a copy of a student or a course; the copy narration proves it.
- Enrolment refuses: a full section, a duplicate enrolment, an unknown section, and a student already in another section of the same course.
- Dropping an enrolment unlinks it, and the student and section both survive.
- A student who leaves is removed from every roster, and the program prints the number of rosters changed.
- A section's room booking cannot overlap another section's in the same room — with the rule enforced in one place.
- The timetable is time-ordered and stable, with a documented tie-break.
- An instructor's load is reported, and an instructor over a documented limit is refused a new section.
- Room bookings for a week are printed in a documented order.
- A
constdepartment prints every report.
Constraints¶
- Header and source files per class family, with a documented build command.
- No smart pointers in this lab (Module 11 will convert it). Ownership is expressed by value membership, and sharing by reference — with every reference's owner documented.
- No templates.
- No
friend, no inheritance except one honest is-a if you find one — and justify it if you use it. - Narration in every constructor, copy constructor and destructor.
- No warnings.
Starter task¶
In relationships.md, before coding:
- Write the multiplicities for every pair of types: student↔section, section↔course, section↔room, instructor↔section.
- For the many-to-many, decide where the enrolment record lives, and what it holds: references to both sides, or indices, or something else. Give the cost of each.
- Predict the destruction order when the department is destroyed while enrolments exist.
- Answer: is a room owned by the department, shared among sections, or referenced by bookings? Write the member declaration that expresses your answer.
Implementation tasks¶
- Implement the value types (student, course, room, instructor) with narration.
- Implement the enrolment and the many-to-many, without copying either side.
- Implement the refusals, the dropping, and the student-leaves path.
- Implement the reports and the room-booking overlap rule.
- Write
mainwith the test cases, and print the roster count after a student leaves. - Compare the narration with your prediction, and record the differences.
Test cases¶
| # | Action | Expected result |
|---|---|---|
| 1 | Enrol a student in a section | Enrolment created; both sides show it |
| 2 | Enrol the same student again | Refused |
| 3 | Enrol into a full section | Refused, naming capacity and current count |
| 4 | Enrol the same student in two sections of one course | Second refused |
| 5 | Print a student's timetable | Time-ordered, with a documented tie-break |
| 6 | Print a section's roster | Every enrolled student, once each |
| 7 | Change a student's fee, then print all reports | The same new value everywhere — no stale copies |
| 8 | Drop an enrolment | Unlinked; student and section both still narrate nothing on destruction |
| 9 | A student leaves | Roster count changes for every section affected, printed |
| 10 | Book a room for an overlapping time | Refused, naming the clash |
| 11 | Total enrolment for a course | Equals the sum of its sections' rosters |
| 12 | Print a const department |
Compiles, same output |
| 13 | Copy narration during enrolment | Zero copies of a student or a course |
Boundary cases to run deliberately: a section with capacity 1; a student with no enrolments; a course with no sections; a room with one booking.
Expected behaviour¶
[dept ctor] Data Science
[student ctor] 2024003 Ayesha Khan fee 120000
[section ctor] CS-201 section A room R-1 capacity 2
[enrol ctor] 2024003 -> CS-201/A at 20260414
enrol 2024004 -> CS-201/A -> accepted (1 of 2)
enrol 2024005 -> CS-201/A -> refused: full (2 of 2)
student fee through the department report: 120000
student fee through the section roster: 120000
fee changed to 150000 -> both reports now print 150000
student 2024003 leaves -> rosters changed: 1
no copies of a student were made: copy narration count 0
Extension challenge¶
Add waitlists per section and automatic promotion when somebody drops.
Record in relationships.md:
- Where the waitlist lives, and what it holds — references, enrolments in a state, or something else. Give the cost of each option in one line.
- What happens if a student on a waitlist drops out of the system entirely, and how your design finds out.
- Which of your reports now needs a new line, and how many files that touched.
- Whether the promotion rule belongs to the section or to the department, and why.
Debugging challenge¶
The reports disagree about a student's fee, and the model looks correct:
class Section {
public:
void enrol(Student student) { // by value, "so the section owns its students"
students_.push_back(student);
}
int total_fees() const {
int total = 0;
for (const Student& student : students_) {
total += student.fee();
}
return total;
}
private:
std::vector<Student> students_;
};
Reproduce it — enrol a student in two sections, change the fee, print both rosters — and answer:
- Why do the two reports disagree, and which copy is stale?
- Which relationship did the code create, and which one did the scenario need?
- What is the smallest change that fixes it, and what does it cost in lifetime risk (who must now outlive whom)?
- Which test case in the table catches it?
Reflection questions¶
- Where is the enrolment record stored, and does either side own it? Say what happens to it when one side disappears.
- Changing the fee once and seeing it in every report is the evidence that nothing is copied. Did your copy narration confirm it?
- Which reference in your model has the weakest lifetime guarantee, and what would you write in a comment to warn a reader?
- The room-booking rule is enforced in one place. Which class owns it, and what does it need to know about the other classes?
- Your many-to-many is a container on each side, or a container of enrolments, or both. What are the duplicate risks, and how did you handle them?
- If the department had two campuses with shared courses, which relationships would change, and which ones would stay exactly as they are? Answer with the multiplicities you would write.
Completion checklist¶
- [ ] It builds from a documented command with
-Wall -Wextra -Werrorand no warnings - [ ]
relationships.mdhas the diagram, multiplicities and the owned/shared/referenced decision per relationship - [ ] No student or course is copied into a container — the copy narration proves it
- [ ] The fee read through different paths reports the same value after a change
- [ ] Enrolment refuses all four invalid cases, each with its reason
- [ ] Dropping unlinks, and neither side is destroyed
- [ ] A leaving student updates every affected roster, with the count printed
- [ ] The room-booking overlap is refused, with the rule in one place
- [ ] All thirteen test cases run, with output recorded
- [ ] The debugging challenge was explained before being fixed
- [ ] I can state what is destroyed and what is only unlinked when any object leaves
Related: Lab index · Module 9 — Composition and Association · Previous: Practice Lab 17 · Next: Practice Lab 19 — Dynamic collection: Rule of Three to Rule of Five