Chain of Witnesses
Apologetic question: "Has the faith been continuously believed?"
Project 11 — Chain of Witnesses
“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
Chapter: 11 — Pointers, this, and Dynamic Memory
Due: End of Week 11
Submit: A link to your code — an OnlineGDB project URL or a public GitHub repo URL — with witnesses.cpp as the main source file. See Appendix D for the full workflow.
Allowed tools: Everything through Chapter 11 — classes, pointers, new/delete, linked structures, this.
Not yet allowed: Inheritance (Chapter 12).
The Setup
There is a question a serious Christian sometimes faces: has the Christian faith been continuously believed since the time of the Apostles, or was it invented, corrupted, lost, or fundamentally changed somewhere along the way? It’s a real question — sometimes asked from outside Christianity, sometimes from within. The historical answer is a chain of witnesses: an unbroken record of public testimony from the first century to today, with each generation pointing back to the one before, citing the same core sources, professing the same core gospel.
That chain is a linked list. Quite literally — each witness points to the witnesses before them, often by name, often by direct quotation. Polycarp cites his teacher John. Irenaeus cites his teacher Polycarp. Athanasius cites Irenaeus. Augustine cites Athanasius. Aquinas cites Augustine. Luther cites Augustine and Bernard of Clairvaux. Walther (the founder of LCMS in America) cites Luther. C.S. Lewis cites Augustine, Aquinas, Pascal, Bunyan, MacDonald. The chain is real and the chain is documented.
Your job this week: build it in code. A linked list of Witness nodes, each one a real person who left a real public testimony.
Two LCMS-aware guardrails before you start:
1. This is not Roman-style apostolic succession. That doctrine — that ordained authority is transmitted by physical contact through an unbroken chain of bishops — is a different claim, and confessional Lutherans reject it. We are modeling the historical continuity of public testimony, not a sacramental chain of authority. The shape is similar; the metaphysical claim is not.
2. Don’t invent quotes. Every testimony field you add to your chain must be either a real quote you can cite, or a one-sentence summary of what the witness actually wrote. Plausible-sounding fiction is worse than nothing. If you can’t find a real source for a witness, leave them out and find one you can.
Learning Targets
By completing this project, you will demonstrate that you can:
- Use pointers correctly —
nullptrchecks, dereferencing, arrow access. - Allocate and free memory with
newanddelete. - Build a linked list by hand: insert, traverse, remove, free.
- Use
thisinside a class method. - Write a destructor that walks a chain and frees every node.
- Confirm zero memory leaks via
valgrindor AddressSanitizer. - Handle linked list edge cases (head removal, empty list, single-element list).
Normal Tier
Goal: A singly linked list of Witness nodes with add, remove, print, and a leak-free destructor.
Required features
-
The
WitnessNode (declared inside theChainclass):struct Node { string name; int century; string testimony; Node* next; }; -
The
Chainclass:class Chain { private: Node* head; int count; public: Chain(); Chain(const Chain&) = delete; // forbid copying — see Ch 11 §11.8 Chain& operator=(const Chain&) = delete; void add(string name, int century, string testimony); void remove(string name); void print() const; int size() const; ~Chain(); };The two
= deletelines are mandatory: without them, a strayChain c2 = c1;anywhere inmainshallow-copies the head pointer and your destructor double-frees the nodes. With them, the compiler refuses to copy a Chain — which is exactly what you want. -
Method behaviors:
addcreates a newNodewithnew, links it into the list (at front is simplest; at end is fine too), incrementscount.removefinds the first node with the matching name, unlinks it,deletes it, decrementscount. Handles the head case (head removed → head is updated). Does nothing gracefully if name not found.printwalks the chain, printing each witness’s name, century, and one-line testimony.sizereturnscount.- Destructor walks the chain and
deletes every node. Zero leaks.
-
Seed data: In
main, add at least 8 real witnesses spanning at least 5 centuries.What
centurymeans here: the century of the witness’s testimony — when they wrote the thing you’re quoting — not the century they were born in. Several of these people lived across a century boundary, so fixing the convention keeps everyone’s data comparable (and keepsmerge_sortedin H2 well-defined). Ignatius was born around 35 but wrote his letters around 107, so he goes in the 2nd century.Suggested set, with the century already resolved for you (use real one-line testimonies — look them up):
Witness Life dates Testimony c. centuryClement of Rome c. 35–99 1 Clement, c. 96 1 Ignatius of Antioch c. 35–108 Letters, c. 107 2 Polycarp of Smyrna c. 69–155 Letter to the Philippians; martyrdom c. 155 2 Irenaeus of Lyons c. 130–202 Against Heresies, c. 180 2 Athanasius of Alexandria c. 296–373 On the Incarnation, c. 318 4 Augustine of Hippo 354–430 Confessions c. 400; City of God c. 413–426 5 Anselm of Canterbury 1033–1109 Proslogion, 1078 11 Thomas Aquinas 1225–1274 Summa Theologiae, c. 1265–1274 13 Martin Luther 1483–1546 95 Theses, 1517 16 Any 8 of these clears the “at least 5 centuries” bar. Substitute other witnesses freely — just apply the same convention and be able to cite your source.
-
Stress test: Add 100 witnesses (use a
forloop with generated names like “Witness 1”, “Witness 2”, …). Print the size. Then remove every odd-numbered one (Witness 1, Witness 3, Witness 5, …) in a loop — 50 removals total. Print the final size. Confirm clean exit. -
Compiles cleanly with
-Wall -Wextraenabled in OnlineGDB compiler settings (org++ -Wall -Wextraif you build locally). No warnings, no errors. -
Zero memory leaks. The destructor must free every node. Verify one of these ways:
- OnlineGDB: in the project’s compiler flags, add
-fsanitize=addressnext to-Wall -Wextra. This is the same box where you set-Wall -Wextrain Appendix D: click the gear icon (⚙) → “Extra Compiler Flags,” and put all three flags in that one box, separated by spaces —-Wall -Wextra -fsanitize=address. Run the program. The bottom panel will report any leaks. None should appear. (As of 2026, OnlineGDB’s GCC supports AddressSanitizer; if a given run rejects the flag, fall back to a localvalgrindor ASan build, below.) - Local: compile with
g++ -Wall -Wextra -fsanitize=address witnesses.cpp -o witnessesor run undervalgrind --leak-check=full ./witnesses. - Mention which check you ran in your reflection comment block.
- OnlineGDB: in the project’s compiler flags, add
Normal-tier rubric (out of 100)
| Criterion | Points |
|---|---|
Compiles cleanly with -Wall -Wextra | 10 |
Chain class with add, remove, print, size, destructor | 25 |
add correctly inserts a new node with new | 10 |
remove handles head case, middle case, and not-found case | 15 |
| Destructor frees every node — confirmed zero leaks | 15 |
| Stress test adds 100, removes 50, prints correct size | 10 |
| Seed data uses at least 8 real witnesses with verifiable info | 10 |
| OnlineGDB/GitHub link + reflection comment block | 5 |
Medium Tier (+up to 25% extra credit)
M1. Find and modify
Add Node* find(string name) const returning a pointer to the first node matching name, or nullptr if not found.
Coach’s Note — Note we’re returning the raw
Node*from a method, even thoughNodeis a private nested struct. This is the kind of API design tradeoff that gets uncomfortable in real C++ — you’re leaking an implementation detail. For Project 11 it’s fine; we’re learning. In Chapter 12 and beyond you’ll see cleaner patterns.
M2. insert_after and move_to_front
Add:
void insert_after(string existing_name, string new_name, int new_century, string new_testimony)— find the named witness and insert a new node right after them.void move_to_front(string name)— find the witness, unlink them from their current position, and re-insert at the head.
For move_to_front, use this explicitly somewhere — e.g., return *this to allow chaining. Demonstrate chaining in main:
chain.move_to_front("Augustine").move_to_front("Athanasius");
(For chaining, the method needs to return Chain&. One-line hint: // return *this; at the end of move_to_front to enable chaining.)
M3. Reject duplicates
Modify add so it refuses to add a witness whose name (case-insensitive) is already in the chain. Print a rejection message.
Hard Tier (+up to 25% additional extra credit)
Pick ONE of H1, H2, or H3 as your Hard-tier feature. H4 (zero verified leaks) is required if you attempt any Hard tier — it’s the discipline that makes the rest of it worth grading.
H1. Doubly linked + reverse
Add a Node* prev field to each node. Maintain it correctly through add, remove, insert_after. Track a Node* tail pointer, updated alongside head whenever the structure changes. Add void print_reverse() const (walks tail→head) and void reverse() (flips the chain in place — no new calls, just swap prev/next on every node and swap head/tail).
Used in context: trace the chain backwards from a modern witness to the Apostles by reverse-ing first.
H2. Merge sorted
Add void merge_sorted(Chain& other) that consumes other’s nodes into this chain. After the merge, the combined chain is sorted by century (ascending) and other is empty (all its nodes were moved, none copied).
This is the kind of operation that demonstrates real pointer manipulation — you’re rewiring nodes between two chains, not allocating any new ones. Get it right and you’ve graduated from beginner pointer mechanics.
H3. Stewardship of state (your choice)
Pick one small structural feature you’ve designed yourself. Document the design choice in your reflection. Examples: a find_by_century(int) returning a Node* (non-owning) for the first witness in that century; a count_by_century_range(int lo, int hi); a to_array() that returns a freshly allocated string[] of all witness names (caller takes ownership — document this). Whatever you pick should rewire or read pointers, not just call existing methods.
H4. Zero memory leaks (verified) — required for any Hard tier
Run your final program with -fsanitize=address in OnlineGDB (or under valgrind on Linux). Confirm zero leaks across a meaningful demo (add 8+, remove a few, exercise your chosen Hard feature, free).
Mac users: do this check in OnlineGDB, not locally. ASan’s leak detection is disabled on macOS (
detect_leaks is not supported on this platform) and valgrind doesn’t run on Apple Silicon at all — so a clean local run proves nothing about leaks. See Chapter 11 §11.9.
Include the tool output as a comment block at the bottom of your source file (or in your repo README if you used GitHub).
Submission
Submit one URL via the course portal:
- OnlineGDB project link (recommended for Coding 1 and Coding 2). Create your project at onlinegdb.com, set compiler flags to
-Wall -Wextrain the project settings, build your solution, and share the link. See Appendix D for the full workflow. - 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 the code compiles when the grader checks it out.
What the linked project must contain
- The main source file —
witnesses.cpp— containing your full solution. - 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.
*/
- The program left in a “demonstrable” state — when the grader presses Run, the features for your targeted tier should be exercised. Hard-code inputs at the top of
main()(or pre-fill OnlineGDB’s Stdin panel) so the grader doesn’t have to guess what to type.
That’s it. No separate demo.txt. No screenshots. The instructor will open your link, read the comment block, run the program, and grade against the rubric.
Coach’s Note — Coding 1 and Coding 2 focus on writing code, not managing development environments. If something behaves oddly, you and the grader are looking at the exact same browser-hosted environment — there are no “works on my machine” defenses by design. Coding 3 will introduce a local toolchain properly.
Hints
- “My remove crashes when I remove the head.” The head case is special. You need to update
headitself, not thenextof some previous node. Special-case it first; then the general loop handles the rest. - “My destructor crashes.” You’re probably walking the chain and dereferencing a node after deleting it. Always save
nextbeforedelete:while (current != nullptr) { Node* next = current->next; delete current; current = next; } - “My reverse loop doesn’t terminate.” You’re probably overwriting
current->nextbefore you’ve saved it. Trace it on paper, three nodes long, with values you can name. - “ASan reports a leak.” Read the report. It tells you the file and line of the
newthat wasn’t freed. The fix is almost always: add adeleteon the right path. - “I want to use
std::list<Witness>instead of a hand-built linked list.” Don’t, this week. The whole point is feeling the mechanics. You can uselistfor fun after you ship. - “How long should this take me?” Normal: 5–9 hours. Medium: 8–12 hours. Hard: 10–14 hours (with H4, pick one of H1/H2/H3). Hard tier was intentionally narrowed from the original spec — the broader version was eating freshmen alive.
What Mastery Looks Like
A great Project 11 has zero memory leaks. ASan or valgrind report all clean, including under the stress test and the merge.
A great Project 11 has honest data. Every witness is real. Every testimony is either a quote or a faithful one-line summary. The grader could verify any entry against a printed source.
A great Project 11 has correct pointer handling on every code path. The destructor frees all nodes. Removal correctly handles head, middle, tail, and not-found. Insert and merge correctly rewire next/prev pointers.
A great Project 11 has a main that stress-tests the chain. Adding 100 nodes, removing 50, merging another chain — all of it without crashes, all of it leak-free. That’s what tells the grader the pointer code is correct.
A great Project 11 is proud of the data shape. You’re not just demonstrating a linked list. You’re holding the historical chain of Christian witness in memory. Print it, look at it, and notice: that’s a real thing, made legible by code.
When You’re Done
- Read
witnesses.cppaloud. Each method has clear control flow. - Run under ASan. Confirm clean.
- Verify at least 3 of your witnesses’ sources by hand.
- Update README.
- Submit.
- Read Chapter 12. Inheritance arrives.
Coach’s Note — The chain of witnesses is real and it’s checkable. You can walk into any university library and verify, source by source, that the chain you built in memory corresponds to actual documents written by actual people in actual centuries. That’s the kind of thing that’s hard to do with most apologetic claims, and it’s the kind of thing apologetics looks like when it’s done with care. Cherish the data shape. You’ve made it visible.
See you on Monday.