Chain of Witnesses & Argument Case File
Apologetic question: "Has the faith been continuously believed — and what kinds of arguments are there?"
Project 5 — Chain of Witnesses & Argument Case File
“Therefore, since we are surrounded by so great a cloud of witnesses, let us also lay aside every weight, and sin which clings so closely, and let us run with endurance the race that is set before us.” — Hebrews 12:1
Every
newis a promise. Everydeleteis you keeping it.
Chapter: 6 — Pointers, Dynamic Memory, and Inheritance
Due: End of Week 6
Submit: A link to your code — an OnlineGDB project URL or a public GitHub repo URL — with p5_witnesses.cpp as the main source file. See Appendix A for the full workflow and where the compiler-flag box lives.
Allowed tools: Everything through Chapter 6 — all prior C++, plus pointers, nullptr, new/delete, hand-built linked lists, ->, base and derived classes, protected, virtual, override, virtual destructors, and pure virtual functions.
Not yet allowed: Java (that’s Chapter 7), STL containers (vector, list, map), smart pointers (unique_ptr, shared_ptr, make_unique).
Estimated time: Normal 7–9 hrs · Medium 9–12 hrs · Hard 12–15 hrs
The Setup
Say it plainly before you start: this is the hardest project in this course. It merges two full projects from the sixteen-week edition into one build, in the week where the language stops holding your hand about memory. Nothing later in this book is harder than this — Week 7 hands you Java’s garbage collector and Week 8 is the final. If you are going to reserve a clean, unhurried block of hours for one project all term, reserve it for this one, and start it on day three of the week, not day six.
Two real questions sit underneath it.
The first: has the Christian faith been continuously professed since the Apostles, or was it invented, lost, or fundamentally changed somewhere along the way? The historical answer is a chain of witnesses — a documented record in which each generation points back to the one before, by name and by quotation. Polycarp cites John. Irenaeus cites Polycarp. Augustine cites Athanasius. Aquinas cites Augustine. Luther cites Augustine. That is a linked list. Not metaphorically — structurally. Each node holds a person, and a pointer to the next one.
The second: is there any argument for God worth taking seriously? The honest answer given by the tradition is: not one argument — several, working together, each strong where the others are weak. Cosmological, moral, teleological, ontological. They share a shape — premises, a characteristic move, a conclusion — and differ entirely in what they actually argue. That is a base class and four subclasses.
Here is the unification, and it is the whole design of this project. You are building one data structure: a hand-built singly linked list whose nodes hold pointers to a polymorphic base type. Each Node owns an Argument*. The actual object on the other end of that pointer is a Cosmological, a Moral, a Teleological, or an Ontological. One traversal loop walks the chain and calls defend() on every node — and gets four different answers, because the loop dispatches on what each object actually is, not on the type of the pointer holding it.
That single loop is this entire week in one place: pointers (walking next), dynamic allocation (every node and every argument came from new), and dynamic dispatch (one call, four behaviors). Chapter §6.9 argued that pointers and inheritance belong in one week. This project is the proof.
Coach’s Note — This is also where memory bugs first bite for real. Up to now a mistake produced a wrong number. Here a mistake produces a program that crashes ten lines away from the actual error, or one that works perfectly on your machine and leaks a hundred allocations on the grader’s. Before you write a line, read §6.19 — Common Bugs (Week 6 Edition) all the way through. Fourteen entries, every one with the real compiler or sanitizer text. You are going to see at least four of them this week. Reading them first turns each one from a two-hour mystery into a thirty-second lookup.
Two guardrails, restated from §6.23 because they matter more than the code:
1. This is not Roman-style apostolic succession. That doctrine — ordained authority transmitted by unbroken physical contact through a chain of bishops — is a different claim, and confessional Lutherans reject it. You are modeling the historical continuity of public testimony, which is checkable in any library catalogue. Same shape, different metaphysical claim.
2. Do not invent citations. Every source string in your program must name a real text where that person actually made that argument. Plausible-sounding fiction is worse than an empty field. The seed table below gives you six you don’t have to hunt for.
Learning Targets
By completing this project, you will demonstrate that you can:
- Use pointers correctly —
nullptrchecks, dereferencing, arrow access, and initializing every pointer you declare. - Allocate with
newand release withdelete, and state who owns each allocation. - Build a singly linked list by hand: insert at the tail, traverse, remove, and free.
- Handle every linked-list edge case: head removal, tail removal, middle removal, not-found, and empty.
- Write a destructor that walks a chain and frees two things per node without ever reading freed memory.
- Design a class hierarchy with a meaningful base and concrete subclasses, using
protectedfor shared data. - Chain derived constructors to a base constructor through the initializer list.
- Use
virtual,override, and a virtual destructor, and explain what breaks without each one. - Store derived objects polymorphically behind base-class pointers and dispatch over them in a single loop.
- Confirm zero memory leaks with AddressSanitizer, and read the report when there are some.
Normal Tier
Goal: One program, p5_witnesses.cpp, containing a four-family Argument hierarchy and a hand-built Chain whose nodes own Argument* values — traversed, searched, edited, stress-tested, and freed with zero leaks.
Required features
1. The base class Argument.
class Argument {
protected:
string label; // the family: "Cosmological", "Moral", ...
string witness; // the person who made it
string source; // the text where they made it
int century; // century of the TEXT, not of the birth
public:
Argument(string l, string w, string s, int c);
virtual ~Argument(); // MUST be virtual — see §6.14
virtual void defend() const; // a short default body
void header() const; // not virtual — shared by everyone
string get_witness() const;
string get_label() const;
int get_century() const;
};
The four fields are protected on purpose: each subclass’s defend() reads witness directly (§6.11). header() prints two lines — the family, the person, the century, and the source. The base defend() gets an ordinary one-line body (something like (no characteristic defense recorded for this family)) so the file compiles and runs before you have written a single subclass. Build it that way. Get a chain of plain Argument objects walking and freeing first, then add the subclasses.
The century convention is the chapter’s: the century of the testimony — when the text you are citing was written — not the century the person was born in.
2. Four concrete families, each deriving publicly from Argument, each chaining to the base constructor through its initializer list, each marking its defend() with override:
Cosmological— from contingency and causation.Moral— from the reality of obligation.Teleological— from design and order toward ends.Ontological— from the analysis of the concept itself.
Each constructor takes (string witness, string source, int century) and supplies its own label to the base — a Cosmological always labels itself "Cosmological". Each defend() prints three lines: two premises and a conclusion line that names witness.
Note what defend() is and is not. It states the family’s shape, which is genuinely shared by everyone in that family — that is exactly why the family is a class. The specific person and the specific text are printed by header(), from data. Do not put words in a specific thinker’s mouth inside defend().
3. The Chain class — hand-built, no STL:
class Chain {
private:
struct Node {
Argument* arg; // OWNED by this node
Node* next;
};
Node* head;
Node* tail;
int count;
public:
Chain();
Chain(const Chain&) = delete; // mandatory — see §6.7
Chain& operator=(const Chain&) = delete; // mandatory
~Chain();
void add(Argument* a); // TAKES OWNERSHIP of a
bool remove(string witness_name); // true if something was removed
Argument* find(string witness_name) const; // NON-OWNING, or nullptr
void present_all() const;
void print_roster() const;
int century_span() const;
int size() const;
};
Behaviors:
addallocates aNodewithnew, stores the argument pointer in it, and appends at the tail so the chain prints in the order you added it. Incrementscount. TheChainnow owns that argument — the caller must neverdeleteit.removefinds the first node whose argument’s witness matches, unlinks it,deletes the argument first and then the node, decrementscount, returnstrue. Returnsfalsegracefully if the name isn’t there. It must handle four cases: head, middle, tail, and not-found. The tail case is the one that will hurt you; read the Hints before you write it.findwalks the chain and returns a borrowedArgument*, ornullptr. Put the words// NON-OWNING: do not delete the result.directly above it. That comment is graded.present_allis the payoff loop: one traversal, callingheader()and thendefend()through anArgument*, producing four different defenses from one call site.print_rosteris a compact one-line-per-argument listing — century, family, witness — for checking your work after edits.century_spantraverses and returnslatest century - earliest century, or0on an empty chain.sizereturnscount.- The destructor walks the chain, saves
nextbefore the deletes, releases the argument and then the node, setsheadandtailback tonullptr, and prints one summary line reporting how many it freed. That number must equal whatsize()reported just before. It is your own self-check, and you will use it.
4. Seed data. In main, add at least six real arguments spanning at least four centuries and covering all four families. Use this table so you don’t have to go hunting — the source column is literally the string that goes in the field:
| Family | witness | source | century |
|---|---|---|---|
Ontological | Anselm of Canterbury | Proslogion 2-3, 1078 | 11 |
Cosmological | Thomas Aquinas | Summa Theologiae I, Q.2, A.3 - the Third Way, c. 1265-1274 | 13 |
Ontological | Rene Descartes | Meditations on First Philosophy, Meditation V, 1641 | 17 |
Teleological | William Paley | Natural Theology, 1802 - the watchmaker | 19 |
Moral | C.S. Lewis | Mere Christianity, Book 1, 1952 | 20 |
Ontological | Alvin Plantinga | The Nature of Necessity, 1974 - the modal form | 20 |
Every one of those is a real person, a real text, and a real argument you can verify in any library catalogue. Substitute or extend freely — just keep the citation checkable and keep the century convention. (Yes, the ontological family has three entries and the cosmological one. That reflects which attributions this book is willing to hand you without hedging, not a claim about which argument is strongest.)
5. A main that demonstrates every requirement, with no input needed. In this order:
- Seed the chain, then print
size()andcentury_span(). - Call
present_all(). - Call
find()twice — once for a witness who is in the chain (print theirheader()through the borrowed pointer), once for a name that isn’t ("Bertrand Russell"works), and confirm you gotnullptr. - Call
remove()four times: on the head, on a middle node, on the tail, and on a name that isn’t there. Print what each one returned. - Print
size(),century_span(), andprint_roster(). addone more argument. This is the tail test. If yourremovelefttailpointing at a freed node, this line is where the program corrupts itself. Print the roster again and confirm the new argument is at the end.- Stress test: in a loop, add 100 generated arguments (
"Stress Witness " + to_string(i), with a source string clearly marked as generated sample data, not a citation). Print the size. Then remove the 50 odd-numbered ones in a loop. Print the size. - Return from
mainand let the destructor do the cleanup.
6. Compiles cleanly with -Wall -Wextra in OnlineGDB’s Extra Compiler Flags box (see Appendix A). Zero warnings. In this project a warning is usually the actual bug — -Wdelete-non-virtual-dtor is the compiler telling you your destructors are half-firing.
7. Zero memory leaks, verified by adding -fsanitize=address to that same flags box in OnlineGDB, which runs Linux. Run the whole demo. The report must be empty. State in your reflection block which check you ran and what it said.
If you build locally on a Mac, this check does not work. ASan’s leak detection is disabled on Darwin and valgrind does not run on Apple Silicon at all, so a clean local run proves nothing about leaks — the tool never looked. Do the leak check in OnlineGDB. See §6.8.
Example run
This is the actual, unedited output of the reference solution, built with g++ -std=c++17 -Wall -Wextra (zero warnings) and run. Your wording inside defend() will differ; the structure and the numbers should not.
=== Chain of Witnesses & Argument Case File ===
Chain holds 6 arguments, spanning 9 centuries.
--- Presenting the chain (one loop, four kinds of argument) ---
[Ontological] Anselm of Canterbury (century 11)
source: Proslogion 2-3, 1078
1. Start from the idea of a being than which nothing greater can be conceived.
2. Ask whether that idea, analyzed carefully, can be of something that does not exist.
=> Anselm of Canterbury concludes: it cannot; such a being exists.
[Cosmological] Thomas Aquinas (century 13)
source: Summa Theologiae I, Q.2, A.3 - the Third Way, c. 1265-1274
1. Nothing in the universe contains the reason for its own existence.
2. A series of things that do not explain themselves explains nothing.
=> Thomas Aquinas concludes: something exists necessarily.
[Ontological] Rene Descartes (century 17)
source: Meditations on First Philosophy, Meditation V, 1641
1. Start from the idea of a being than which nothing greater can be conceived.
2. Ask whether that idea, analyzed carefully, can be of something that does not exist.
=> Rene Descartes concludes: it cannot; such a being exists.
[Teleological] William Paley (century 19)
source: Natural Theology, 1802 - the watchmaker
1. Some things are ordered toward ends they did not choose.
2. Order aimed at an end is the signature of intention.
=> William Paley concludes: the ordering points to an orderer.
[Moral] C.S. Lewis (century 20)
source: Mere Christianity, Book 1, 1952
1. Some obligations bind whether or not anyone consents to them.
2. A rule nobody may opt out of is not a preference.
=> C.S. Lewis concludes: obligation points past us to a standard we did not set.
[Ontological] Alvin Plantinga (century 20)
source: The Nature of Necessity, 1974 - the modal form
1. Start from the idea of a being than which nothing greater can be conceived.
2. Ask whether that idea, analyzed carefully, can be of something that does not exist.
=> Alvin Plantinga concludes: it cannot; such a being exists.
--- Borrowing one argument back out of the chain ---
[Moral] C.S. Lewis (century 20)
source: Mere Christianity, Book 1, 1952
find("Bertrand Russell") returned nullptr, as it should.
--- Removing the head, a middle node, the tail, and a name that isn't there ---
remove("Anselm of Canterbury") -> removed
remove("William Paley") -> removed
remove("Alvin Plantinga") -> removed
remove("Bertrand Russell") -> not found
Chain now holds 3, spanning 7 centuries:
century 13 [Cosmological] Thomas Aquinas
century 17 [Ontological] Rene Descartes
century 20 [Moral] C.S. Lewis
--- Adding after a tail removal (this is where a stale tail bites) ---
Chain now holds 4:
century 13 [Cosmological] Thomas Aquinas
century 17 [Ontological] Rene Descartes
century 20 [Moral] C.S. Lewis
century 19 [Teleological] William Paley
--- Stress test ---
after 100 adds: size = 104
after 50 removes: size = 54
Leaving main. The Chain destructor runs now:
[Chain destructor] released 54 arguments and 54 nodes.
Read the last two numbers together. size() said 54 and the destructor freed 54. If those two disagree, you have a bug right now, before ASan has said a word — either your count bookkeeping is wrong or your destructor is not reaching the end of the chain.
Grading rubric — Normal (out of 100)
| Criterion | Points |
|---|---|
Compiles cleanly with -Wall -Wextra — zero warnings | 6 |
Argument base: protected fields, initializer-list constructor, virtual defend(), virtual ~Argument() | 9 |
Four concrete families, each chaining to the base constructor, each defend() marked override | 11 |
Chain skeleton: private nested Node holding Argument* and Node* next, plus head/tail/count and both copy operations = deleted | 8 |
add allocates the node with new, takes ownership of the argument, appends at the tail | 7 |
remove handles the head, middle, tail, and not-found cases, and frees the argument then the node | 12 |
find returns a borrowed Argument* or nullptr, documented as non-owning | 5 |
present_all — one traversal loop producing four different defenses through a base pointer | 9 |
size, print_roster, and century_span all report correct values | 5 |
Destructor frees every node and every argument, saving next before the deletes, and reports its count | 9 |
Zero memory leaks, verified with -fsanitize=address in OnlineGDB | 6 |
| Seed data: at least 6 citable arguments, at least 4 centuries, all four families represented | 5 |
| Stress test: 100 adds, 50 removes, correct final size, clean exit | 4 |
| Reflection block including the ownership paragraph, plus a working submission link | 4 |
Total: 100.
Medium Tier (+up to 25% extra credit)
Do all three. Each one is small; together they turn the base class abstract and the chain editable.
M1. respond_to_skeptic() — and an abstract base
Add to Argument:
virtual void respond_to_skeptic() const = 0;
The = 0 makes Argument abstract (§6.16). You can no longer write Argument a; — the compiler will refuse, and it will list exactly which methods you still owe. Every one of the four families must now implement respond_to_skeptic(), printing the most common objection to that family and a real, humble response. Not “and that’s why the skeptic is wrong.” A response an actual thoughtful person would give and an actual thoughtful skeptic would recognize as an answer.
Call it from present_all() right after defend().
M2. strength_against and pick_best
Add virtual int strength_against(string objection) const to Argument, returning a 1–10 score. Each family overrides it. Suggested (these numbers are pedagogical, not authoritative — say so in a comment):
| Objection | Cosmological | Moral | Teleological | Ontological |
|---|---|---|---|---|
"the universe is uncaused" | 8 | 3 | 5 | 5 |
"morality is just preference" | 3 | 9 | 4 | 4 |
"the concept of God is incoherent" | 4 | 4 | 4 | 7 |
"this is all just emotional reasoning" | 6 | 5 | 6 | 5 |
Then add Argument* Chain::pick_best(string objection) const — traverse, call strength_against on each, return a borrowed pointer to the strongest (or nullptr on an empty chain). Demonstrate it in main against all four objections. Note what this is really showing: pick_best has no idea what kinds of argument are in the chain. It just dispatches and compares.
M3. Pointer surgery: insert_after and move_to_front
bool insert_after(string existing_witness, Argument* a)— find the node whose witness matches, splice a new node in immediately after it, take ownership ofa. Returnfalse(anddelete ayourself, since you refused ownership — say so in a comment) if the name isn’t found. If you insert after the current tail,tailmust move.Chain& move_to_front(string witness_name)— unlink that node from wherever it is and re-link it at the head, allocating nothing. ReturningChain&(viareturn *this;) lets calls chain:
chain.move_to_front("C.S. Lewis").move_to_front("Thomas Aquinas");
That return *this; is this earning its keep (§6.4). After both calls, Aquinas is first and Lewis is second. Verify with print_roster().
Hard Tier (+up to 25% additional extra credit)
Pick ONE of H1, H2, or H3. One feature, done cleanly and documented, demonstrates the skill better than three done badly — and this project is already the largest in the course. H4 is required for any Hard credit; it is the discipline that makes the rest worth grading.
If you want the cheapest pairing: H1 sets up H3, because H3 needs a concrete class derived from a concrete class and H1 builds exactly that. A second completed H feature earns +5 bonus, if you genuinely have the hours.
H1. A third level in the hierarchy
Add class FineTuning : public Teleological — the cosmological fine-tuning form of the design argument, as distinct from Paley’s biological watchmaker. Override defend() again, at the deeper level, so it prints the fine-tuning case specifically rather than the general teleological one. (Yes, a derived class can override an already-overridden virtual.)
Demonstrate constructor chaining through three levels — FineTuning → Teleological → Argument — and add a trace line to each constructor and destructor so you can watch the order in the output. It must be outside-in on the way in and inside-out on the way out (§6.12). Paste that trace into your reflection block.
Attribution rules still apply: cite a real text for whatever fine-tuning source you name, or state in a comment that the entry is illustrative sample data for the exercise.
H2. merge_sorted
Add void merge_sorted(Chain& other) that consumes other’s nodes into this chain so that afterward:
- The combined chain is sorted by
century, ascending. otheris empty —other.size() == 0,other.head == nullptr,other.tail == nullptr.- No
newwas called and nodeletewas called. Not one. Every node was moved by rewiring pointers.
Requirement 3 is the whole exercise. If you allocate, you have written a copy, not a merge. Verify by giving main a second chain of three arguments, merging, printing the roster (centuries must be non-decreasing), printing both sizes, and letting both destructors run — the emptied chain’s destructor must report freeing 0.
This is the operation that separates “I can follow a linked-list example” from “I can manipulate pointers.”
H3. Demonstrate object slicing on purpose
Write two functions:
void inspect_by_value(Teleological a); // by VALUE — sliced
void inspect_by_reference(const Teleological& a); // by REFERENCE — intact
Each calls defend() on its parameter. Now build a FineTuning (from H1, or add any small concrete-derived-from-concrete pair purely for this demo) and hand the same object to both. The by-value call prints the Teleological defense; the by-reference call prints the FineTuning defense. There is no warning and no error — the compiler is perfectly happy (§6.15).
Paste both output lines into your reflection block and write one sentence explaining, in your own words, why the by-value version is wrong. This is one of the easiest C++ bugs to introduce by accident and one of the hardest to see.
H4. Verified zero leaks — required for any Hard credit
Run your final program — Normal demo, Medium features, and your chosen Hard feature all exercised in one run — under -fsanitize=address in OnlineGDB. Confirm the report is empty. Paste the tool’s output as a comment block at the bottom of your source file (or in your repo README, if you submitted GitHub).
A clean run of a program that only exercises the Normal tier does not count. Leak checks are only as good as the code path they cover.
Submission
Submit one URL via the course portal:
- OnlineGDB project link (recommended). Create your project at onlinegdb.com, set Extra Compiler Flags to
-Wall -Wextra(and-fsanitize=addresswhile you’re testing), build, and share the link. Appendix A has the exact click path. - GitHub repo link (optional). If you’ve set up local development on your own, push the source to a public repo and submit that URL. You’re responsible for making sure it compiles when the grader checks it out — and remember that the leak check still has to be done on Linux.
What the linked project must contain
- The main source file —
p5_witnesses.cpp— containing your full solution. One file. This book’s convention. - A reflection comment block at the very top of that file. This week it has two extra required lines:
/*
* Tier targeted: Normal / Medium / Hard
* Features done: list each feature you completed
* Who owns what: REQUIRED THIS WEEK. For every kind of allocation in your
* program, name the owner and the exact line that frees it.
* Example: "Every Argument* is owned by the Node that holds
* it. Nodes are owned by the Chain. Both are freed in
* ~Chain (and in remove(), for one node). find() and
* pick_best() return borrowed pointers that nobody deletes."
* Leak check: which tool, run where, and exactly what it reported
* What I learned: one short paragraph (no bullets)
* What I'd change: one sentence
* AI usage: where and how, if any. Be honest.
*/
The ownership paragraph is graded and it is not busywork. If you cannot write it, you do not yet know whether your program is correct — you know that it happened to run.
- The program left in a demonstrable state — when the grader presses Run, every feature of your targeted tier is exercised with no typing required. No stdin, no menus, no guessing.
That’s it. No separate demo.txt. No screenshots.
Coach’s Note — You and the grader are looking at the exact same browser-hosted environment, which is why there are no “works on my machine” defenses in this course by design. That cuts both ways this week: the grader’s leak report is the same one you can run yourself, right now, before you submit. Run it.
Hints
These are the things that actually go wrong, in roughly the order they will happen to you.
“My program crashed and the output stopped mid-sentence.” Print the pointer immediately before you use it: cout << "DEBUG head=" << head << " current=" << current << endl;. 0 means null. A plausible hex value means it points at something. Absurd garbage means it was never initialized. You now know which of the three problems you have, which is 80% of the fix. Then rebuild with -fsanitize=address and let the sanitizer name the line.
“remove works, then the very next add corrupts everything.” This is the bug of this project, and it is worth reading twice. When you remove the last node, head and the previous node’s next are fine, but tail still points at the node you just freed. The next add executes tail->next = n; — writing through a dangling pointer. On a good day it crashes. On a bad day it silently works and you ship it. Under ASan the diagnosis is unambiguous:
==22019==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000178
The fix is two lines inside remove, before the deletes:
if (current == tail) {
tail = previous; // previous is nullptr when the chain had one node — correct
}
Note the single-node case falls out for free: previous is nullptr, so head and tail both become nullptr and the chain is properly empty.
“My destructor crashes.” You are reading a node after freeing it. Save next first, every time, with no exceptions:
while (current != nullptr) {
Node* next = current->next; // BEFORE the deletes
delete current->arg;
delete current;
current = next;
}
“ASan says I leaked, and it points at a new Ontological(...) line in main.” The report names the allocation site, not the missing delete. You freed the node but not the argument it owned. Every node in this project owns two allocations’ worth of cleanup: delete current->arg; then delete current;. Both, in that order, in the destructor and in remove.
“My subclass’s defend() never runs — no error, no warning, just the base version.” Two possible causes, both silent. Either defend() isn’t virtual in the base, or you didn’t write override on the derived one and its signature quietly doesn’t match (a missing const is the usual culprit). Write override on every override and the second failure becomes a compile error you can read. See §6.13.
“The compiler warned me about a non-virtual destructor and I ignored it.” Don’t. -Wdelete-non-virtual-dtor means every delete through an Argument* runs only ~Argument. Your derived destructors never fire. It is a warning, not an error; your program builds and appears to work; and without -Wall you would not have been told at all. That is the whole reason this course requires a zero-warning build.
**“error: request for member 'witness' in 'p', which is of pointer type"** — you used .where you need->. GCC literally asks you maybe you meant to use ’->’ ?`. Yes. You did.
“error: use of deleted function 'Chain::Chain(const Chain&)'” — good news, actually: it means your = delete lines are working and something tried to copy a Chain. Usually you passed one to a function by value. Change the parameter to const Chain&.
A worked numeric check, so you can grade yourself. Two of them, in fact:
century_span()on the six seed entries must print9. The centuries are 11, 13, 17, 19, 20, 20. The earliest is 11, the latest is 20, and20 - 11 = 9. If you get20you returned the maximum instead of the difference. If you get11you returned the minimum. If you get0your loop never ran, which meansheadwas null when you thought it wasn’t. After the three removals in step 4, the remaining centuries are 13, 17, 20, so the span must print7.- The size arithmetic must land on 54. Start with 6, remove 3 (the fourth removal finds nothing and changes nothing), add 1 back: that is 4. Add 100: 104. Remove the 50 odd-numbered stress witnesses: 54. Your destructor must then report freeing exactly 54 arguments and 54 nodes. Two independently computed numbers that must agree — if they don’t, stop and fix that before you look at anything else.
“Can I just use std::list<Argument*> or vector?” Not this week. The entire point is feeling the mechanics of the thing the library is hiding. Use whatever you like the moment you have shipped this.
“Can I use unique_ptr so the deletes happen for me?” Same answer, and for the same reason. Next week Java hands you a garbage collector and you get to feel what it’s worth. That feeling only arrives if you did this by hand first.
“How long should this take me?” Normal: 7–9 hours, and that assumes you actually did the chapter’s reps and passed the §6.21 checkpoint at 6 of 8 or better. If you skipped the reps, budget twelve and expect frustration — this is not a project you can reason your way through without the pointer mechanics already in your fingers. Medium: 9–12. Hard: 12–15, Normal and Medium included, with one H feature. Attempting all three H features runs past twenty hours and is not worth it.
Build order that keeps you sane. Do not write the whole file and then compile. In this order, compiling and running after every step: (1) Argument alone, one object on the stack, print its header; (2) Chain holding plain Argument*, with only add, present_all, size, and the destructor — verify the freed count; (3) the four subclasses, and watch present_all produce four different outputs; (4) find; (5) remove, and test the head, middle, tail, and not-found cases separately; (6) the tail-test add; (7) the stress test; (8) the leak check. Eight small green steps beat one large red one, and step 5 is where you will spend a third of your time.
What Mastery Looks Like
A great Project 5 has zero leaks under a demo that exercises every path. Add, remove from all four positions, add again, stress-test, free — and the sanitizer says nothing at all. That silence is the grade.
A great Project 5 knows who owns what, and says so in comments. add says it takes ownership. find says it does not. The destructor frees exactly the two things each node is responsible for. The ownership paragraph in the reflection block reads like someone describing a system they designed, not a program they got working.
A great Project 5 has a present_all() you would not have to change to add a fifth family. The loop dereferences an Argument* and calls a virtual method. It does not know, ask, or care what is actually on the other end. If your loop contains an if testing what kind of argument this is, you have written a switch statement wearing a class hierarchy as a costume — go back and let dispatch do the work.
A great Project 5 has honest data. Every witness is a real person, every source is a real text, and every generated stress-test entry is clearly labeled as generated. A grader could pick any line of your output and find it in a library.
A great Project 5 is humble about what it built. The chain does not conclude “and therefore God exists.” It presents the witnesses and the arguments and stops, which is what the tradition at its best has always done, and which the code models exactly: present_all() presents. The reader does the rest.
And a great Project 5 looks, at the end, like the thing it actually is — the documented chain of people who argued the case, held in your computer’s memory in the same shape history left it in, each one speaking in its own voice from a single loop. You built that out of two pointers and a new.
When You’re Done
- Read
p5_witnesses.cppfrom top to bottom, out loud if you can. Everynewshould have adeleteyou can point at. Every method that returns a pointer should have a comment saying whether the caller owns it. - Run it with
-Wall -Wextra. Zero warnings, not “only two small ones.” - Run it with
-fsanitize=addressin OnlineGDB. Read the report even when it’s empty — you want to recognize what clean looks like. - Check the two numbers:
size()before the destructor, and the count the destructor reports. They must match. - Verify at least two of your citations yourself. A library catalogue search takes ninety seconds.
- Write the ownership paragraph. If it comes out vague, that vagueness is telling you something about your code, not about your writing.
- Submit the link.
- Take the rest of the day off. Seriously — this was the big one, and Week 7 starts a new language.
Coach’s Note — If you are reading this at midnight with a segfault on the screen and a due date in the morning, here is the honest advice: stop debugging and work the ladder in §6.22 in order, starting at rung 3. Print the pointer. Draw the four boxes on paper. Turn on the sanitizer. Those three rungs, in that order, take about fifteen minutes and solve most of what goes wrong this week — and they work at midnight, when nothing else is open. If you are still stuck after that, submit the best compiling version you have with an honest reflection block describing exactly where it breaks and what you tried. A finished Normal beats an abandoned Hard, and a working partial with a clear account of the failure beats a blank submission by a very wide margin.
Next up: Chapter 7. Java arrives, and with it a garbage collector that does §6.5 through §6.8 for you, forever, automatically. It is going to feel like a gift — and it will only feel like a gift because you did this week by hand.