Project 11

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 — nullptr checks, dereferencing, arrow access.
  • Allocate and free memory with new and delete.
  • Build a linked list by hand: insert, traverse, remove, free.
  • Use this inside a class method.
  • Write a destructor that walks a chain and frees every node.
  • Confirm zero memory leaks via valgrind or 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

  1. The Witness Node (declared inside the Chain class):

    struct Node {
        string name;
        int century;
        string testimony;
        Node* next;
    };
  2. The Chain class:

    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 = delete lines are mandatory: without them, a stray Chain c2 = c1; anywhere in main shallow-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.

  3. Method behaviors:

    • add creates a new Node with new, links it into the list (at front is simplest; at end is fine too), increments count.
    • remove finds the first node with the matching name, unlinks it, deletes it, decrements count. Handles the head case (head removed → head is updated). Does nothing gracefully if name not found.
    • print walks the chain, printing each witness’s name, century, and one-line testimony.
    • size returns count.
    • Destructor walks the chain and deletes every node. Zero leaks.
  4. Seed data: In main, add at least 8 real witnesses spanning at least 5 centuries.

    What century means 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 keeps merge_sorted in 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):

    WitnessLife datesTestimony c.century
    Clement of Romec. 35–991 Clement, c. 961
    Ignatius of Antiochc. 35–108Letters, c. 1072
    Polycarp of Smyrnac. 69–155Letter to the Philippians; martyrdom c. 1552
    Irenaeus of Lyonsc. 130–202Against Heresies, c. 1802
    Athanasius of Alexandriac. 296–373On the Incarnation, c. 3184
    Augustine of Hippo354–430Confessions c. 400; City of God c. 413–4265
    Anselm of Canterbury1033–1109Proslogion, 107811
    Thomas Aquinas1225–1274Summa Theologiae, c. 1265–127413
    Martin Luther1483–154695 Theses, 151716

    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.

  5. Stress test: Add 100 witnesses (use a for loop 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.

  6. Compiles cleanly with -Wall -Wextra enabled in OnlineGDB compiler settings (or g++ -Wall -Wextra if you build locally). No warnings, no errors.

  7. Zero memory leaks. The destructor must free every node. Verify one of these ways:

    • OnlineGDB: in the project’s compiler flags, add -fsanitize=address next to -Wall -Wextra. This is the same box where you set -Wall -Wextra in 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 local valgrind or ASan build, below.)
    • Local: compile with g++ -Wall -Wextra -fsanitize=address witnesses.cpp -o witnesses or run under valgrind --leak-check=full ./witnesses.
    • Mention which check you ran in your reflection comment block.

Normal-tier rubric (out of 100)

CriterionPoints
Compiles cleanly with -Wall -Wextra10
Chain class with add, remove, print, size, destructor25
add correctly inserts a new node with new10
remove handles head case, middle case, and not-found case15
Destructor frees every node — confirmed zero leaks15
Stress test adds 100, removes 50, prints correct size10
Seed data uses at least 8 real witnesses with verifiable info10
OnlineGDB/GitHub link + reflection comment block5

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 though Node is 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 -Wextra in 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

  1. The main source filewitnesses.cpp — containing your full solution.
  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 “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 head itself, not the next of 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 next before delete:
    while (current != nullptr) {
        Node* next = current->next;
        delete current;
        current = next;
    }
  • “My reverse loop doesn’t terminate.” You’re probably overwriting current->next before 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 new that wasn’t freed. The fix is almost always: add a delete on 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 use list for 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

  1. Read witnesses.cpp aloud. Each method has clear control flow.
  2. Run under ASan. Confirm clean.
  3. Verify at least 3 of your witnesses’ sources by hand.
  4. Update README.
  5. Submit.
  6. 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.