Module 5 — Lab 5: make the catalogue speak the language¶
Levels: [L2 Apply → L5 Design] · Extends: Lab 4 · Time: 4–6 hours
The scenario¶
The library system works. Items and members hold their invariants, and nothing outside the types can put them into a state their author did not intend. What it cannot do is participate: the catalogue's lookup is a hand-written loop, printing a list means a function that every caller must remember, and sorting is impossible. The desk staff have started asking for "the list in identifier order, printed" and there is no operation that means that.
This lab replaces the loops with operations the standard library can drive, and adds the one piece of class-level state the system needs: identifier allocation.
The deliverable includes a failure. You will write a deliberately invalid comparator first, run a sort, and record what happened. That record is marked, and it is worth more than the working version — because "a library algorithm compiled, ran and gave a wrong answer" is a fact best learned on your own machine.
What you start from¶
- Your Lab 4 solution:
BookandMemberwith encapsulated interfaces, and aCataloguethat owns its items. - Your Lab 3 program as the driver, so the behaviour stays visible.
If your Lab 4 solution still has a mutable accessor or a setter, fix that first — this lab multiplies the damage, because an operator is a public operation and an inconsistent one is a public inconsistency.
Requirements¶
operator==andoperator!=onBook, and the same onMember, consistent with the notion of identity used throughout the system. State identity in one sentence in a comment.operator<forming a strict weak ordering, verified by sorting a list that contains duplicate identifiers and by the property test in Debugging.operator<<producing a one-line readable form, used to print the whole catalogue. It must be a free function takingstd::ostream&, and it must return the stream.- A
staticcounter allocating identifiers, with either an out-of-class definition or the C++17inline staticform, and a comment explaining the choice and naming the alternative. (Interacts with Module 4: the counter is class-level state, and nothing outside the class may set it.) Catalogue::operator[]inconstand non-constforms —at()returning a reference,at()returning aconstreference — with a comment on which one a caller holding aconst Catalogue&gets. (Interacts with Module 4: the non-constform must not let a caller break an invariant. If it does, return a value instead and say why.)- The comparator note — a short written record of the invalid comparator: what you wrote, what the sort did, whether it warned you, what happened after you fixed it, and which property was violated.
- It must build with no warnings using the course's compile command:
g++ -std=c++17 -Wall -Wextra.
What to deliver¶
- A program that prints the catalogue in identifier order, finds an item by identifier with a standard algorithm, and allocates identifiers automatically for newly added items.
- The comparator note, as a Markdown file or a printed session transcript, containing the broken comparator, the observed output, and the fixed version with the difference explained.
- A comment block in the program listing the four attacks from Lab 4 that used to compile and no longer do.
Self-assessment checklist¶
It builds and runs
- [ ] Compiles with
g++ -std=c++17 -Wall -Wextrawith no warnings - [ ] Prints the catalogue in the required order, and the print loop uses
<<only
It is correct
- [ ] The sort is verified with duplicate identifiers present, and both survive
- [ ] The property test (irreflexive, asymmetric, transitive) passes
- [ ]
std::finduses the same notion of identity asstd::sortand printing - [ ] Boundary cases work: an empty catalogue, one item, two identical identifiers
- [ ] The identifier counter never hands out the same number twice, including after a copy — if copying an item allocates a new identifier, that is stated
It is designed
- [ ] I can state each class's identity rule in one sentence
- [ ]
operator<<needs nofriend, or itsfriendis justified in one line - [ ] The
staticcounter cannot be set from outside the class - [ ]
operator[]'s two forms are consistent with Module 4's encapsulation rules, or the non-constform was refused with a reason
It is verified
- [ ] I predicted the sort's output before running it, including for duplicates
- [ ] The broken comparator was run, and its output recorded rather than described
- [ ] I explain the violated property by name, not by "it is wrong"
Common ways this lab goes wrong¶
| Symptom | Cause | What to do |
|---|---|---|
| The sort produces a different order on different runs | the comparator is not a strict weak ordering | property test, then compare the field that defines the order and nothing else |
std::find never finds the item that is visibly there |
== and < define identity differently |
pick one, write the sentence, make all three use it |
| The program links but the counter is always 0 | the static member is declared and never defined |
add the definition, or make it inline static |
Printing produces 0x7ffd… for an item |
operator<< was never found, so the pointer overload was used |
the type is being printed through a pointer; dereference it, and check the operator's signature |
| The catalogue cannot be sorted because it cannot be copied | the copy constructor was deleted, or it holds a pointer | Module 10 and 11 are the answers; for now sort a vector of identifiers |
Adding operator< broke the print loop |
using was forgotten and a name is hidden — or the two operators were written in different units |
check both are visible, then check consistency |
| The comparator note says "it did not work" | the observation was not recorded | rerun the broken version and paste the output; the exercise is the evidence |
Reference solution¶
There is no published file for this lab, on purpose: it is your Lab 4 solution with
four operations added, and the decisions are the work. What follows is the part
none of the module's examples show together — identifier allocation, the const
pair, and printing a whole collection.
// The parts to compare your version against.
class Book {
public:
explicit Book(std::string title) : id_(nextId_++), title_(std::move(title)) {}
Book(int id, std::string title) : id_(id), title_(std::move(title)) {}
int id() const { return id_; }
const std::string& title() const { return title_; }
// Identity: two books are the same book when they have the same identifier.
bool operator==(const Book& other) const { return id_ == other.id_; }
bool operator!=(const Book& other) const { return !(*this == other); }
bool operator<(const Book& other) const { return id_ < other.id_; }
private:
static int nextId_; // declared here…
int id_;
std::string title_;
};
// …defined once, here. `inline static` above would remove this line and is the
// C++17 preference; the classic form is shown because the link error it prevents
// is this module's most common mistake.
int Book::nextId_ = 1000;
std::ostream& operator<<(std::ostream& out, const Book& book) {
return out << '#' << book.id() << " " << book.title();
}
class Catalogue {
public:
bool add(const Book& book);
// Read-only access for a caller holding a `const Catalogue&`.
const Book& at(std::size_t index) const { return items_.at(index); }
// Writable access — and the question this lab asks: does handing out a mutable
// reference let a caller break the catalogue's invariant? If yes, return a copy
// or remove this overload, and say which in a comment.
Book& at(std::size_t index) { return items_.at(index); }
std::size_t size() const { return items_.size(); }
void sortByIdentifier() { std::sort(items_.begin(), items_.end()); }
private:
std::vector<Book> items_;
};
std::ostream& operator<<(std::ostream& out, const Catalogue& catalogue) {
for (std::size_t i = 0; i < catalogue.size(); ++i) {
out << catalogue.at(i) << '\n'; // const overload: no copy
}
return out;
}
Three things to look for when comparing with your version:
- Which notion of identity you chose, and whether all three operations use it.
If your
==compares titles and your<compares identifiers, the lab's requirement 1 is not met, whatever the program prints. - Whether
Book& at(std::size_t)survives your own attack test. Write the line that breaks an invariant through it. If one compiles, either delete the overload or narrow what it returns. - What your comparator note records. "The sort gave the wrong order" is not an observation; the pasted output and the violated property's name are.
Next: Challenge problems — three open problems, including whether an operator should exist at all. · All modules