Practice Lab 2 — Library catalogue in plain structs¶
Difficulty: Beginner · Estimated time: 3 hours · Module 1: C++ and the OOP Mindset
The step between parallel arrays and classes. You group the fields correctly and then discover that grouping data is only half of what a class does.
At a glance¶
| Difficulty | Beginner |
| Estimated time | 3 hours |
| Module | Module 1 — C++ and the OOP Mindset |
| Concepts practised | Structs, grouping related data, free functions over a group, separating identity from behaviour, std::vector and std::string |
| Prerequisite | Practice Lab 1 |
| Class design | you choose the structs; the operations are given |
Scenario¶
A reading room keeps a small shelf of books. Volunteers record each book, lend it out, take it back, and print the shelf so a visitor can find something. There is no catalogue number system yet: the title is the identifier.
The room's volunteer coordinator has read enough bad code to ask for one thing: no parallel arrays. If a book's title and its shelf position are two different arrays, somebody will one day sort one and not the other.
Problem statement¶
Write a program that keeps a shelf of books and supports these operations:
- Add a book: title, author, year, shelf position.
- Lend a book by title, if it is on the shelf.
- Take a book back, if it is currently lent.
- Print the shelf, in title order, marking which books are out.
- Print what is currently lent, with the borrower's name.
- Report how many books are on the shelf and how many are out.
Group each book's data into a struct — the fields that describe one book, in one place. The
operations remain free functions that take the shelf, by reference.
Then, in design-notes.md, answer the question this lab exists to raise: where do the rules about
lending live, and which function would have to change if the rule changed?
Learning objectives¶
By the end of this lab you will be able to:
- Group related data into a struct so that the data for one entity cannot be partly updated.
- Write operations that operate on a collection of structs, and pass it by reference.
- Distinguish identity (which book this is) from state (whether it is out, and to whom).
- Explain why a struct plus free functions is not yet encapsulation, using an example from your own code in which an invariant can be broken from outside.
- Say what would have to change in your program if a book could be lent to two people at once.
Functional requirements¶
- A shelf holds any number of books, in no particular insertion order.
- A new book with a title already on the shelf is refused, with a message — the title is the identifier.
- Lending a book that is already out is refused, naming the current borrower.
- Lending a book that is not on the shelf is refused, naming the title.
- Returning a book that is not out is refused.
- The shelf listing is sorted by title, and shows each book's status on one line.
- The "out" listing is sorted by title, and shows the borrower.
- The counts are always consistent:
on the shelf + out == total. - An empty shelf is not an error: both listings print a clear "nothing" line and the counts are zero.
Constraints¶
- One source file, compiled with
g++ -std=c++17 -Wall -Wextra -Werror main.cpp -o shelf. - No classes —
structwith public data members, and free functions. (A class is next module; using one now would hide the lesson.) std::vectorandstd::stringare expected: this lab is about grouping, not about memory.- Every refusal prints a message to
std::cout— not tostd::cerr— so that the expected-output comparison in this lab's table works, and so the tests can be scripted. - The lending rules must appear in one function each, not duplicated in the menu loop.
- No warnings.
Starter task¶
Write the structs and the function declarations only. Then answer in design-notes.md:
- Which fields describe what a book is, and which describe what has happened to it? Split your struct accordingly in your notes — you do not have to split it in code.
- A volunteer writes
shelf.at(0).on_loan = false;in the menu loop without setting the borrower's name. What is now true of the program, and which function should have been called? - Which single value could you derive instead of storing, if you had to? What would that cost on each listing?
Implementation tasks¶
- Define the structs and the shelf.
- Write
add_book,lend,give_back,print_shelf,print_loansandcounts. - Write the menu loop, calling the functions rather than re-implementing their rules.
- Run every test case and record the output.
- Write
design-notes.mdwith the three starter questions answered and one extra: which invariant can be broken by the person using this program's functions, and how?
Test cases¶
| # | Action | Expected result |
|---|---|---|
| 1 | Add three books, list the shelf | Three lines, in title order |
| 2 | Add a duplicate title | Refused, naming the title |
| 3 | Lend a book that is on the shelf | Accepted; the borrower is recorded |
| 4 | Lend the same book again | Refused, naming the current borrower |
| 5 | Lend a title that is not on the shelf | Refused, naming the title |
| 6 | Return a book that is out | Accepted; the borrower is cleared |
| 7 | Return a book that is not out | Refused |
| 8 | List the shelf after lending | The book is marked out, the rest unchanged |
| 9 | Counts after two lends | shelf 1, out 2, total 3 |
| 10 | Counts on an empty shelf, both listings | nothing on the shelf / nothing is out, counts 0 0 0 |
Boundary cases to run deliberately: a book lent and returned twice; a title that differs only in case
(compilers and Compilers) — decide whether they are the same book, and write the decision down.
Expected behaviour¶
$ ./shelf
1) add 2) lend 3) return 4) list 5) out 6) counts 7) quit
> 1
title: Compilers
author: Aho et al.
year: 2006
shelf position: A3
added.
> 2
title: Compilers
borrower: Bilal
lent.
> 4
* Compilers (Aho et al., 2006) shelf A3 [OUT: Bilal]
Thinking in Systems (Meadows, 2008) shelf B1 [on shelf]
> 6
shelf 1, out 1, total 2
> 7
Extension challenge¶
Add a second copy of a title, so the same title can appear twice and two people can borrow it.
Notice what breaks: the title is no longer an identifier, find-by-title is no longer unique, and the
concept of "a copy" has appeared. Record in design-notes.md:
- The fields you had to add, and to which struct.
- The functions whose meaning changed, and how.
- The identifier you chose for a copy, and why.
- Which of the six operations now needs two identifiers instead of one.
This is the change that will make you want a Copy class in a later lab. Write down the sentence that
would describe it.
Debugging challenge¶
Two volunteers edit the shelf at the same time and the counts stop adding up:
Find the cause in your own code. You have three places that could be wrong — the lend, the return, and one of your own choosing. Answer:
- Which single operation can produce this line, and what does it do that no other operation does?
- Which invariant is
shelf + out == totalan instance of, and where is it not enforced? - What would have to change so that this line cannot be printed at all?
Do not patch the counts function to "fix" the arithmetic. The count is a symptom.
Reflection questions¶
- Which struct fields would you want to make unreachable from outside, and why?
lendandgive_backare the only operations that change the state fields. Is that enough to make the invariant safe? Why not?- A volunteer asks for a "sort by author" listing. How many functions does that touch, and where should the ordering rule live?
- If a book had a status that could be
on shelf,out,reservedorlost, what would you store, and what would you worry about? - Name the objects in this problem, and for each one say which of your free functions would become its operations.
- Which of your functions takes the shelf and something else, and would be better as a function of one object?
Completion checklist¶
- [ ] It compiles with
-Wall -Wextra -Werrorand no warnings - [ ] No parallel arrays anywhere — one struct per book
- [ ] All ten test cases run, with output recorded
- [ ] Boundary cases run, including the two books whose titles differ only in case
- [ ] Both lending rules live in one function each, and the menu loop calls them
- [ ]
design-notes.mdanswers all three starter questions and names one breakable invariant - [ ] The extension challenge was attempted and its findings recorded
- [ ] The debugging challenge was reproduced and explained before being fixed
- [ ] I can say which of my functions would become a method, and on which object
Related: Lab index · Module 1 — C++ and the OOP Mindset · Previous: Practice Lab 1 · Next: Practice Lab 3 — Rectangle and geometry model