Project 4

Stewardship Account & Apologetics Library

Apologetic question: "What does Christianity say about money — and about the life of the mind?"

Project 4 — Stewardship Account & Apologetics Library

Chapter: 5 — From Struct to Class: Objects and Encapsulation Due: End of Week 5 Submit: A link to your code — an OnlineGDB project URL or a public GitHub repo URL — with p4_library.cpp as the main source file. See Appendix A for the full workflow. Allowed tools: Everything through Chapter 5: all prior C++, plus class, public/private, member functions, this, constructors (default and parameterized), destructors, and composition (an object holding objects). Not yet allowed: Pointers, new/delete, inheritance, virtual, Java. Estimated time: Normal 6–8 hrs · Medium 8–11 hrs · Hard 11–14 hrs

“Each one must give as he has decided in his heart, not reluctantly or under compulsion, for God loves a cheerful giver.” — 2 Corinthians 9:7

“Of making many books there is no end, and much study is a weariness of the flesh.” — Ecclesiastes 12:12

Two notes on that “Not yet allowed” line, because Chapter 5 handed you two things that look like exceptions and are not. this (§5.8) is a pointer and it is yours already — use it wherever a parameter shadows a field. The pointer-returning finder from §5.16 is also a legitimate Chapter 5 tool, but this project is specified so that you never need it: every required method below is described in terms of array indices and references. If you find yourself writing Book*, stop — you are solving a harder problem than the one you were given. Everything else about pointers waits for Chapter 6.


The Setup

There is a Christian word for handling resources that are not fundamentally your own: stewardship. What you have is entrusted, and the entrustment carries rules. A “bank account” that lets any passing line of code set the balance to whatever it likes is not an account. It is a sticky note.

You saw the sticky note in §5.1. LooseAccount had a rule — a balance should never go negative — and no way to enforce it, so the rule lived scattered across every function that touched a balance, held in place by nothing but your memory. TightAccount moved the same rule inside the class, where the compiler enforces it for you. That is the whole argument of this chapter, and this project is where you have to make the argument yourself instead of reading it.

So: a small lending library, funded by its members’ stewardship.

The Apologetics Library owns two things: a shelf of books, and a roster of members. Each member carries a borrowing card and a giving fund — a stewardship account administered by the library. Money goes into the fund. Gifts to the library’s acquisitions budget come out of it. So do the member’s own study expenses, which are recorded separately, because a gift and a purchase are not the same transaction even when the dollar amount is identical.

The fund has two rules it will not break:

  1. The balance never goes negative. You cannot give or spend what is not there.
  2. The balance never exceeds the fund’s ceiling. The ceiling is a number the member chooses once, when the fund is opened. After that, the class enforces it — not the member’s memory, not a comment, not a note in main. A fund designated for giving is not a place to accumulate, so a deposit that would push the balance over the ceiling is refused entirely. Not clamped. Refused.

Rule 2 is the one that matters pedagogically, and it is the one a struct could not have given you at any price. A struct would let you write maya.balance = 4000; from twelve different places in your program and never say a word. Your class has to say no — out loud, in the transcript, with the balance printed afterward to prove nothing moved.

The library half is the same idea at system scale. Book, Member, and Library are three classes that cooperate only through public methods. Library never touches Book::available. Member never touches StewardshipAccount::balance. Each class stays in charge of its own data and asks its neighbors politely — and when a neighbor says no, the answer is honored and reported, not worked around.

Two direct words before you start.

This project does not endorse a prosperity-gospel reading of generosity. Nothing in your program rewards a member for giving. There is no bonus, no interest, no thank-you multiplier. The class records, accurately, what was given. That is the entire design, and it is what stewardship has always actually meant.

The class enforces arithmetic, not conscience. It refuses overdrafts because an overdraft is a broken invariant. It refuses over-ceiling deposits because the member said so when they opened the fund. It has no opinion whatsoever about whether a member gives enough, and you should not write one into it. The class makes the record honest. The person decides what to do with the truth.

The books are real books, listed below so you can paste them in. Use those, or substitute other real ones — real titles, real authors, spelled correctly.


Learning Targets

By completing this project, you will demonstrate that you can:

  • Declare classes with private data and public methods, and design the method set from a problem description rather than from a handed-down signature list.
  • Write constructors — default and parameterized — that make an object valid the instant it exists, using initializer lists in declaration order.
  • Write a destructor that reports meaningful state at the end of an object’s life.
  • Enforce an invariant the class refuses to violate, including refusing an operation and returning false instead of silently correcting it.
  • Use composition — a Member that has a StewardshipAccount, a Library that has arrays of Book and Member.
  • Coordinate three or four objects through their public interfaces only, taking objects by Book& when you must change them and const Member& when you must not.
  • Mark every query const, and explain why the compiler cares.
  • Write a main that exercises a system without once reaching inside it.

Normal Tier

Goal: One file, p4_library.cpp, containing four classes — StewardshipAccount, Book, Member, Library — and a main that opens the library, lends books, moves money, gets told no several times, and closes.

Two files in the chapter’s code/ directory are warm-up skeletons, and starting from them is expected rather than cheating: stewardship_starter.cpp has the account with give() and spend() stubbed out, and library_starter.cpp has Book complete, Member half-stubbed, and Library left to you. Neither is the project. Both save you the blank-page hour.

Required features

1. StewardshipAccount — the class that refuses.

class StewardshipAccount {
private:
    string owner;
    double balance;
    double ceiling;
    double total_given;

public:
    StewardshipAccount();                                              // default
    StewardshipAccount(string owner_name, double starting, double fund_ceiling);
    bool deposit(double amount);
    bool give(double amount);
    bool spend(double amount);
    double get_balance() const;
    double get_ceiling() const;
    double get_total_given() const;
    string get_owner() const;
    void print_ledger() const;
};
  • The default constructor sets owner to "", and the three numbers to 0.0. It exists because Library will declare an array of Members, each of which contains one of these. Without it, nothing compiles (§5.15, and bug 5 in §5.19).
  • The parameterized constructor uses an initializer list, in declaration order. It refuses a starting balance above the ceiling: say so and open the fund at 0.00. A negative ceiling becomes 0.0.
  • deposit returns false and prints one line of explanation if amount <= 0, or if balance + amount would exceed ceiling. Note carefully: the test is on the resulting balance, not on the amount. Otherwise it adds and returns true.
  • give returns false and explains if amount <= 0 or amount > balance. Otherwise it subtracts from balance, adds to total_given, and returns true.
  • spend has the same two guards as give and subtracts from balance — and does not touch total_given. That is the entire difference and it is the point. Do not implement one by calling the other.
  • All four getters and print_ledger are const.

2. Book.

class Book {
private:
    string title;
    string author;
    bool available;

public:
    Book();
    Book(string t, string a);
    void check_out();
    void return_book();
    bool is_available() const;
    string get_title() const;
    string get_author() const;
    void print() const;
};

Straight out of §5.14. Both constructors are required; the default one is what makes Book books[MAX_BOOKS]; legal.

3. Member — composition.

The three array bounds below are the ones this project uses. Put them at the top of your file, above every class, so they are in scope everywhere:

const int MAX_BORROWED = 2;
const int MAX_BOOKS    = 12;
const int MAX_MEMBERS  = 6;

Two books per member is small on purpose — it makes the borrowing limit easy to hit and therefore easy to show in your run. (library_starter.cpp ships with MAX_BORROWED = 5; change it to 2.)

class Member {
private:
    string name;
    string borrowed_titles[MAX_BORROWED];
    int borrowed_count;
    StewardshipAccount fund;           // <-- a Member HAS-A account

public:
    Member();
    Member(string n, double starting, double ceiling);
    bool borrow(Book& book);
    bool give_back(string title, Book& book);
    bool add_funds(double amount);
    bool donate(double amount);
    bool spend(double amount);
    string get_name() const;
    int get_borrowed_count() const;
    double get_total_given() const;
    void print_card() const;
};
  • The parameterized constructor builds the fund in the initializer list: : name(n), borrowed_count(0), fund(n, starting, ceiling). There is no moment at which a Member exists with an invalid fund.
  • borrow refuses if the book is unavailable, and refuses if the member already holds MAX_BORROWED books. On success it calls book.check_out(), records the title, increments the count, returns true. The parameter is Book& — a reference — because you are changing the real book on the shelf, not a copy (§5.14, point 1).
  • give_back finds the title in borrowed_titles, shifts the rest of the array down over it, decrements the count, calls book.return_book(), returns true. Returns false with an explanation if the member does not hold that title.
  • add_funds, donate, spend are three-word delegations to fund.deposit, fund.give, fund.spend. They return whatever the fund returned. Member does not second-guess the account and does not re-implement its rules.
  • print_card prints the name, the borrowed titles one per line, and then calls fund.print_ledger().
  • Every getter and print_card are const.

4. Library — the container that coordinates.

class Library {
private:
    string name;
    Book books[MAX_BOOKS];
    int book_count;
    Member members[MAX_MEMBERS];
    int member_count;
    double donations_received;

    int find_book_index(const string& title) const;    // -1 when not found
    int find_member_index(const string& who) const;    // -1 when not found

public:
    Library(string n);
    bool add_book(const Book& b);
    bool add_member(const Member& m);
    bool checkout(const string& who, const string& title);
    bool give_back(const string& who, const string& title);
    bool add_to_fund(const string& who, double amount);
    bool receive_donation(const string& who, double amount);
    bool pay_expense(const string& who, double amount, const string& what);
    void print_catalog() const;
    void print_members() const;
    ~Library();
};
  • The two finders are private. Nothing outside the Library needs them, and §5.2’s “methods are (mostly) public” is exactly this case. They return an index, or -1 for “not here” — the same honest not-found signal as nullptr, without needing a pointer.
  • add_book and add_member refuse politely when the shelf or roster is full, and never write past the count.
  • The five coordinating methods all follow the same shape: find, check, delegate, report. Each distinct failure gets its own message and its own return false — “no such member” is not the same problem as “no such book,” which is not the same problem as “that book is already out.”
  • print_catalog and print_members loop to book_count / member_count — never to MAX_BOOKS / MAX_MEMBERS. Count, do not guess (§5.15).
  • ~Library() prints the closing report, computed at close time from the arrays, not from a number you saved earlier: how many books, how many are still on loan, how many members, total donations received, and the sum of every member’s total_given. Those last two are computed from completely different places — one is the Library’s running total, the other is a fresh sum over the members’ accounts. If they disagree, one of your methods is lying. That is a free correctness check; use it.
  • Do not give Book, Member, or StewardshipAccount a printing destructor at Normal tier. Library declares arrays of them, so you would get a wall of messages from the empty slots. Doing it properly is Medium tier M2.

5. Seed data — paste this in.

Six real books:

TitleAuthor
Mere ChristianityC.S. Lewis
The Reason for GodTimothy Keller
ConfessionsAugustine of Hippo
PenseesBlaise Pascal
Law and GospelC.F.W. Walther
The Resurrection of the Son of GodN.T. Wright

(Spell Pensées without the accent in your source. Non-ASCII characters in string literals are a portability fight you do not need this week.)

Three members:

NameOpening balanceFund ceiling
Maya40.00100.00
Marcus15.0050.00
Grace0.0025.00

6. A main that demonstrates the whole system, in this order:

  1. Open the library, add all six books, add all three members, print the catalog.
  2. Maya checks out Mere Christianity, then Confessions, then tries Penseesrefused, she is at the two-book limit.
  3. Marcus tries Mere Christianityrefused, already out. Then checks out Pensees — fine.
  4. A checkout for a member who does not exist — refused. A checkout of a book not in the catalog — refused.
  5. Maya adds 75.00 to her fund — refused, it would put her over the ceiling. Then adds 50.00 — fine.
  6. Maya gives 500.00refused, more than her balance. Then gives 25.00 — fine.
  7. Maya pays a 12.50 expense. Her balance drops; her given to date does not move.
  8. Grace gives 5.00 from a balance of 0.00refused. Marcus gives 10.00 — fine.
  9. Maya returns Confessions. Then tries to return it again — refused, it is not on her card.
  10. Print every member’s card and the catalog again, then let main end so the destructor report prints.

7. main never touches a private field, never contains an if that checks a balance or an availability flag, and never re-implements a rule that belongs in a class. Every rule lives in a method. The grader checks this specifically.

8. Compiles cleanly with g++ -std=c++17 -Wall -Wextra — or with -Wall -Wextra set in OnlineGDB’s compiler settings. Zero warnings. Bugs 6, 12, and 15 in §5.19 are all warnings that produce genuinely broken programs.

Example run

This is the actual, unedited output of a Normal-tier solution built to this spec, compiled with g++ -std=c++17 -Wall -Wextra (zero warnings) and run with no input. Your wording, spacing and indentation will differ — that is fine. If you use the seed data above and run the operations in the order above, your numbers must match these exactly.

=== Opening ===
  Catalog (6 books):
    'Mere Christianity' by C.S. Lewis (available)
    'The Reason for God' by Timothy Keller (available)
    'Confessions' by Augustine of Hippo (available)
    'Pensees' by Blaise Pascal (available)
    'Law and Gospel' by C.F.W. Walther (available)
    'The Resurrection of the Son of God' by N.T. Wright (available)

=== Lending ===
  Maya checked out 'Mere Christianity'.
  Maya checked out 'Confessions'.
    Refused: Maya already has 2 books out; 'Pensees' stays on the shelf.
    Refused: 'Mere Christianity' is already checked out.
  Marcus checked out 'Pensees'.
    Refused: no member named Jordan.
    Refused: 'The Screwtape Letters' is not in the catalog.

=== The fund refuses ===
    [fund] Refused: $75.00 would put Maya at $115.00, over the $100.00 ceiling. Balance stays $40.00.
  Maya added $50.00 to the giving fund.
    [fund] Refused: Maya cannot give $500.00 from a balance of $90.00.
  Maya gave $25.00 to acquisitions.
  Maya spent $12.50 on her own copy of Confessions (an expense, not a gift).
    [fund] Refused: Grace cannot give $5.00 from a balance of $0.00.
  Marcus gave $10.00 to acquisitions.

=== Returns ===
  Maya returned 'Confessions'.
    Refused: 'Confessions' is not on Maya's card.

=== Cards ===
  Members (3):
    Maya (1 out)
      - Mere Christianity
      fund: $52.50 of a $100.00 ceiling | given to date: $25.00
    Marcus (1 out)
      - Pensees
      fund: $5.00 of a $50.00 ceiling | given to date: $10.00
    Grace (0 out)
      fund: $0.00 of a $25.00 ceiling | given to date: $0.00

=== Catalog at close ===
  Catalog (6 books):
    'Mere Christianity' by C.S. Lewis (checked out)
    'The Reason for God' by Timothy Keller (available)
    'Confessions' by Augustine of Hippo (available)
    'Pensees' by Blaise Pascal (checked out)
    'Law and Gospel' by C.F.W. Walther (available)
    'The Resurrection of the Son of God' by N.T. Wright (available)

=== main is done ===

[Concordia Apologetics Library closed]
  books:   6 (2 still on loan)
  members: 3
  donations received: $35.00
  members report given: $35.00

Read the last two lines again. $35.00 was arrived at twice by two unrelated routes — the Library’s running total, and a fresh sum across three member accounts — and it agreed. That agreement is your evidence that no rule got bypassed anywhere in the run.

And read the two [fund] Refused: lines. Both print the balance immediately afterward, and in both cases it is unchanged. That is the required demonstration: not that your class has a rule, but that it exercised the rule in front of the grader.

Grading rubric — Normal (out of 100)

CriterionPoints
Compiles cleanly with g++ -std=c++17 -Wall -Wextra — zero warnings8
StewardshipAccount: private fields, default + parameterized constructors, initializer list in declaration order10
The refusals — over-ceiling deposit, over-balance gift, over-balance expense, non-positive amounts: each rejected, explained, and returning false14
give and spend are genuinely distinct — only give moves total_given, and neither is implemented by calling the other8
Book complete; Member holds a StewardshipAccount as a field (composition), built in the initializer list10
Member::borrow and give_back coordinate with the real Book through Book&, not a copy10
Library owns both arrays plus their counts; private index finders; every loop runs to the count, never to the MAX10
Library’s coordinating methods report each distinct failure separately and return false8
~Library() prints the closing report, computed from the arrays at close time7
Every field private; every query method const; main never reaches inside a class5
main runs the required demo end to end, with each refusal followed by proof the balance did not move6
Real books correctly spelled + reflection comment block + a link the grader can actually open4

Points sum: 8 + 10 + 14 + 8 + 10 + 10 + 10 + 8 + 7 + 5 + 6 + 4 = 100.


Medium Tier (+up to 25% extra credit)

M1. A transaction log inside the fund

Give StewardshipAccount two more private fields and nothing public to reach them with:

private:
    string log[40];
    int log_count;

Every successful deposit, give, and spend appends one line — "DEPOSIT +$50.00", "GIVE -$25.00", "EXPENSE -$12.50". Refused operations append nothing; a refused operation did not happen. Extend print_ledger() to print the log underneath the balance line.

Building the string needs a number turned into text. to_string(amount) from <string> is the direct route, and it gives you six decimal places (25.000000), which is ugly but true. Fixing that cleanly is the kind of small annoyance worth ten minutes of your own experimenting.

This is encapsulation at full strength: the log lives entirely inside the class, main cannot read it, cannot append to it, cannot forge it, and can only ever see it through a method the class controls.

M2. Chatty lifecycle, done correctly

Give Book, Member, and StewardshipAccount printing destructors — and then handle the problem that creates. Library declares Book books[12] and Member members[6], so the empty slots will each announce their own destruction and bury your real output.

The fix, and the whole lesson: guard the message. A Book destructor prints only when title is non-empty. A Member’s prints only when name is non-empty. A StewardshipAccount’s prints only when owner is non-empty.

Before you run it, predict the order on paper — which object dies first, and where the Library’s own destructor body sits relative to its members’ destructors. Then run it and compare. lifecycle_trace.cpp from §5.13 is your reference for the rules. Getting this prediction right is worth more than the extra credit.

You will also see each member’s line print twice — once for the Library’s copy and once for the local object back in main. That is not a bug. §5.14 point 3 told you the Library holds copies; this is what that looks like from the outside.

M3. Duplicates refused, case-insensitively

add_book refuses a title already on the shelf. add_member refuses a name already on the roster. Both comparisons are case-insensitive, so "mere christianity" is caught. Write a small helper that lower-cases a string — a loop, tolower from <cctype>, one character at a time — and use it in both finders.

Then demonstrate it: try to add Confessions twice and show the second attempt bouncing off.


Hard Tier (+up to 25% additional extra credit)

H1. The hold queue

Give Book a waiting list:

private:
    string waiting[3];
    int waiting_count;

When Library::checkout is refused specifically because the book is already out, put the member’s name on that book’s queue (refusing a duplicate name, and refusing when the queue is full). When the book is returned, the Library announces the next name in line and — if that member has a free slot — checks it out to them automatically, shifting the queue down.

Every part of this is Chapter 5 tooling: an array with a count, a shift-down loop, and methods talking to methods. The design question worth thinking about is which class owns the queue — the Book, or the Library. Put a sentence in your reflection block defending your answer.

H2. The audit

Add void Library::audit() const. It is a diagnostic, not a verdict, and it reports three things:

  1. Ledger agreement. donations_received versus the sum of every member’s total_given. Report both numbers and whether they match. (They only diverge if a rule got bypassed — which is the point of checking.)
  2. Orphaned loans. Every book marked checked out whose title appears on no member’s card. That state is impossible if every checkout went through Member::borrow, so finding one is proof that somewhere a rule got routed around. This is the most valuable check in the project: it verifies an invariant across objects, which no single class can do alone.
  3. Ceiling pressure. Every member whose balance is within 10% of their ceiling — a fund that is nearly full is a fund that will start refusing deposits, and the member should probably know before it happens.

Item 3 is a notification, not a judgement. Write it that way. The class has no opinion about anyone’s generosity and neither does your output.

H3. The flex move

Find one C++ feature this course has not covered, use it deliberately, and explain it in your reflection block. Strong candidates for this project:

  • explicit on the one-argument-shaped constructors, so C++ stops silently converting a string into a Member (§5.11 mentions this and then leaves it alone).
  • Library(const Library&) = delete; — forbid copying the library outright. Copying a 12-book, 6-member container by accident is a real bug class, and this is the professional way to make it a compile error instead.
  • = default on a default constructor you want but do not want to write.
  • vector<Book> replacing the fixed array. Ship the fixed-array version first. Then feel how much plumbing disappears — the count, the MAX, the “shelf full” branch, all of it.

Submission

Submit one URL via the course portal:

  • OnlineGDB project link (recommended). Create your project at onlinegdb.com, set the compiler flags to -Wall -Wextra in the project settings, build your solution, and share the link. Appendix A walks through the whole workflow, including how to make a share link that actually works for someone who is not logged in as you.
  • GitHub repo link (optional). If you build locally, push the source to a public repo and submit that URL. You are responsible for it compiling when the grader opens it.

What the linked project must contain

  1. The main source filep4_library.cpp — containing your full solution, all four classes and main in that one file.
  2. A reflection comment block at the very top of that file:
/*
 * Tier targeted:    Normal / Medium / Hard
 * Features done:    list each feature you completed
 * What I learned:   one short paragraph (no bullets)
 * What I'd change:  one sentence
 * AI usage:         where and how, if any. Be honest.
 */
  1. The program left in a runnable, demonstrable state. The grader presses Run and your demo executes top to bottom with no typing. Hard-code the seed data in main. Do not require input.

No separate demo.txt. No screenshots. The grader opens the link, reads the comment block, presses Run, and grades against the rubric.

Before you submit, open your own link in a private/incognito window and press Run. A share link that only works while you are logged in is the single most common way to lose points on a project that was otherwise finished.


Hints

Book books[12]; won’t compile — no matching function for call to 'Book::Book()'.” You wrote Book(string, string) and C++ stopped generating the default constructor for you (§5.11). Add Book() : title(""), author(""), available(true) {}. Bug 5 in §5.19 is this exact message, and note where the compiler points: at Library’s constructor, not at the array. The cause is in a different class from the error.

“I called maya.borrow(...) and the library’s catalog didn’t change.” This is the trap of the week. After lib.add_member(maya); there are two Mayas: the local one in main and the Library’s copy in members[0]. add_member copied her in. Everything after that must go through the library — lib.checkout("Maya", "Confessions") — because the library’s copy is the one the catalog knows about. The chapter warned you in §5.14 point 3; this is where it bites.

“My ceiling check lets too much through.” The test is on the resulting balance, not the amount: if (balance + amount > ceiling). Writing if (amount > ceiling) lets a member at $90 deposit $50 into a $100 fund, because $50 is under $100. Trace it with real numbers before you trust it.

“Should I clamp the deposit to the ceiling instead of refusing it?” No, and the reason matters. Clamping silently changes what the caller asked for. The caller says “put $75 in” and the fund quietly puts $60 in, returns true, and now two parts of your program disagree about what happened. Refuse the whole thing and return false. A rule that quietly edits your request is worse than a rule that says no.

Worked numeric check — Maya’s fund. Run this in your head, then against your program. Ceiling $100.00.

StepOperationBalance afterGiven to date
1fund opened with 40.0040.000.00
2add_funds(75.00)refused, 40 + 75 = 115 > 10040.000.00
3add_funds(50.00) → ok, 40 + 50 = 90 ≤ 10090.000.00
4donate(500.00)refused, 500 > 9090.000.00
5donate(25.00) → ok65.0025.00
6spend(12.50) → ok, total_given untouched52.5025.00

Final: balance $52.50, given $25.00. Then donations_received should be $25.00 (Maya) + $10.00 (Marcus) = $35.00, which must equal the sum of all three members’ total_given: 25.00 + 10.00 + 0.00 = 35.00. If step 6 changed the “given” column, your spend is calling give. If step 2 or step 4 changed the balance column, your guard runs after the arithmetic instead of before it.

“My totals are off by a penny.” Not in this project, and here is the honest reason: every number in the seed data is a whole dollar or an exact half (40, 50, 75, 12.50, 25), and double stores those exactly in binary. You will match to the cent. That is luck, not law — change $12.50 to $12.10 and small errors start accumulating, because 0.1 has no exact binary representation. Never write if (balance == 0.0). Compare with <= and >=, which is what your guards already do.

“My money prints as $52.5 instead of $52.50.” #include <iomanip> and put cout << fixed << setprecision(2); as the first line of main — §1.18 from Week 1. It is sticky: set it once and every double printed afterward gets two decimals.

“The same refusal prints twice.” Both the account and the Library are announcing it. Pick one owner for each message. The rule that was broken belongs to the account, so the account should explain it; the Library should print only on success. Two classes narrating one event is a design smell, not a formatting problem.

warning: 'X' will be initialized after [-Wreorder].” Your initializer list is in a different order from your field declarations. C++ initializes in declaration order and ignores your list order, so the code is lying about what happens (§5.12). Reorder the list. Do not silence the warning.

give_back leaves a ghost title on the card.” You decremented the count but did not shift. Copy every element after the removed one down by one, then decrement. Test it by returning the first of two borrowed books, not the last — returning the last one hides the bug completely.

“My main is 60 lines.” Correct. main should read like a script of things that happened at a library: open, add, lend, refuse, give, return, print, close. If it reads like a rulebook — if there is an if in it checking a balance or an availability flag — you have put a rule in the wrong place. Move it into a method.

“How do I plan the hours?” Chapter 5’s Week at a Glance budgets one three-hour session for this project, and that assumes you arrive with the reps done and both starter files already typed and running. Realistically, budget two sessions for Normal. A rough split from building it: StewardshipAccount 1.5 hrs, Book 0.5, Member 1.5, Library 2.5, main plus debugging 1.5. If you are behind, cut Medium and Hard — never the reps. The reps are what make Chapter 6 survivable.

“I am stuck and it is late and nobody is awake.” §5.22 is the ladder, and rungs 1–3 are the ones that solve Week 5 problems. Specifically: read the first error only; rebuild the misbehaving class alone in a fresh OnlineGDB tab with a three-line main; and add a cout at the top of the method printing the fields it is about to use. Week 5’s failure mode is almost always the object is not in the state you think it is in, and printing the state is a thirty-second experiment that ends the guessing.


What Mastery Looks Like

A great Project 4 has a fund that cannot be corrupted. The grader can call the methods in any order, with any values — negative, zero, enormous, exactly at the ceiling — and the balance is never negative, never above the ceiling, and total_given never goes down. Not because the demo happened to avoid those cases, but because there is no path to the data that does not pass through a guard.

A great Project 4 refuses out loud. Silence is not enforcement. When a rule blocks an operation, the method says which rule and returns false, and the caller can react. A void method that quietly does nothing is a bug the student has not found yet.

A great Project 4 has classes that collaborate without trespassing. Library::checkout does not check a book’s available flag; it asks Member::borrow, which asks Book::is_available, and each answer travels back up. Nobody reaches through anybody. You could hand a stranger any one of the four classes and they would be able to use it correctly from the header alone.

A great Project 4 has a main that reads like a day at a library, not like a rulebook — and a destructor whose report you could hand to a board of trustees. Two independently-computed totals that agree is a small thing that a professional would notice, and it is the difference between “it printed something” and “I know it is right.”

A great Project 4 has real data. Real books, real authors, spelled correctly. Someone who knows the field can check your catalog against their own shelf, and it holds up.


When You’re Done

  1. Read p4_library.cpp top to bottom, out loud. Each method does one thing. Each name is a verb or a question. If a method needs a paragraph to explain, split it.
  2. Try to break it from main. Add maya.balance = 9999; and compile. Confirm the compiler refuses with bug 1’s message from §5.19. Then delete the line. That refusal is the thing you built this week; look at it once on purpose.
  3. Check the two totals in your destructor report. They must agree. If they do not, one of your methods changed state without telling the object that tracks it — and you have just found a real bug the way a real engineer finds one.
  4. Walk the worked numeric check in the Hints against your own output, line by line. Balance and given-to-date, all six steps.
  5. Recompile with -Wall -Wextra one final time. Zero warnings, or you have not finished. A warning you ignored is a bug you scheduled.
  6. Open your own share link in a private window and press Run. If it does not work there, it does not work for the grader.
  7. Fill in the reflection block honestly — including the AI line. Then submit.

Coach’s Note — This is the project students look back on as the week OOP clicked. The shift is from “there is a rule and I will remember to enforce it” to “there is a rule and the compiler enforces it for me.” That is not a syntax upgrade; it is a change in what you believe code is for. If it clicked, the next three chapters get easier, because inheritance, polymorphism, and interfaces are all built on top of exactly this. If it has not clicked yet, go back to §5.1 and read struct_vs_class.cpp one more time with your own project in front of you. It usually clicks on the second pass, and there is no prize for the first.

You built a four-class system that will not let you corrupt it — alone, from a written spec, at whatever hour it is where you are. That is what the job actually looks like. Save your link, close the laptop, and get some sleep; Chapter 6 and the memory underneath all of this will still be here when you come back.