🏛️ The Final Project — What We're Building
Thirteen phases, one genuine synthesis project. This isn't a new concept to learn — it's every previous phase, working together on one real system.
A small but genuinely realistic system for tracking a library's items — books and magazines — with checkout/return, search, and persistence between runs. Every module in this phase builds one real piece of it, and every piece draws directly on a specific earlier phase.
| Piece | Draws On |
|---|---|
A LibraryItem base class, with Book and Magazine derived from it | Phase 5 (OOP) and Phase 6 (inheritance/polymorphism) |
A generic Repository<T> for storing and finding items | Phase 9 (templates) and Phase 7 (the STL) |
| Custom exceptions for invalid checkouts | Phase 10 (error handling) |
| Saving and loading the whole library | Phase 11 (file I/O) |
📚 The Core Class Hierarchy
Phase 6's exact pattern — an abstract base class, with derived types providing their own specific behavior through polymorphism.
class LibraryItem { protected: std::string title; bool checkedOut = false; public: LibraryItem(std::string t) : title(t) {} virtual ~LibraryItem() {} // Phase 6 Module 7's rule — essential here virtual std::string itemType() = 0; // pure virtual — Phase 6 Module 6 std::string getTitle() { return title; } bool isCheckedOut() { return checkedOut; } void setCheckedOut(bool value) { checkedOut = value; } };
protected members (Phase 6 Module 2) let Book and Magazine access title and checkedOut directly, while outside code is still restricted to the public getters/setters — Phase 5 Module 6's encapsulation, applied here specifically at the inheritance boundary.
class Book : public LibraryItem { private: std::string author; public: Book(std::string t, std::string a) : LibraryItem(t), author(a) {} std::string itemType() override { return "Book by " + author; } }; class Magazine : public LibraryItem { private: int issueNumber; public: Magazine(std::string t, int issue) : LibraryItem(t), issueNumber(issue) {} std::string itemType() override { return "Magazine, issue " + std::to_string(issueNumber); } };
Both constructors explicitly call LibraryItem(t) — exactly Phase 6 Module 3's derived-constructor pattern, delegating the shared setup to the base class rather than duplicating it.
🗄️ A Generic Repository<T>
Phase 9's template pattern, doing genuinely real work — a single, reusable storage class that could manage ANY type, not just library items.
template <typename T> class Repository { private: std::vector<std::unique_ptr<T>> items; // Phase 8's smart pointers — no manual delete anywhere public: void add(std::unique_ptr<T> item) { items.push_back(std::move(item)); // Phase 8 Module 5 — transferring ownership } T* findByTitle(std::string title) { auto it = std::find_if(items.begin(), items.end(), [&title](const auto& item) { return item->getTitle() == title; }); return (it != items.end()) ? it->get() : nullptr; } int size() { return items.size(); } const std::vector<std::unique_ptr<T>>& all() { return items; } };
template <typename T>— Phase 9 Module 2, letting this ONE class manageLibraryItem, or genuinely anything else, without rewriting it.std::vector<std::unique_ptr<T>>— Phase 8 Module 9's exact pattern, combining the STL (Phase 7) with automatic memory management.std::find_ifwith a lambda — Phase 7 Modules 6 and 8, an STL algorithm customized with an inline function.std::moveinadd()— Phase 8 Module 4, transferring ownership into the vector rather than copying.
🚨 Custom Exceptions for Real Validation
Phase 10's exception hierarchy, applied to a genuine business rule: you can't check out an item that's already checked out.
class ItemAlreadyCheckedOutError : public std::runtime_error { public: ItemAlreadyCheckedOutError(std::string title) : std::runtime_error("'" + title + "' is already checked out") {} }; class ItemNotFoundError : public std::runtime_error { public: ItemNotFoundError(std::string title) : std::runtime_error("No item titled '" + title + "' was found") {} };
This is precisely Phase 10 Module 3's custom-exception pattern — inheriting from std::runtime_error, building a genuinely meaningful message in the constructor. Two SEPARATE types let calling code react differently to each situation, applying Phase 10 Module 4's multiple-catch-blocks pattern.
void checkOut(Repository<LibraryItem>& repo, std::string title) { LibraryItem* item = repo.findByTitle(title); if (item == nullptr) { throw ItemNotFoundError(title); } if (item->isCheckedOut()) { throw ItemAlreadyCheckedOutError(title); } item->setCheckedOut(true); }
Notice this checks for nullptr (Phase 4 Module 9) BEFORE calling isCheckedOut() — dereferencing a null pointer here would be the exact undefined-behavior danger that module warned about.
💾 Persisting the Library to Disk
Phase 11's ofstream/ifstream pattern, adapted for a genuinely polymorphic collection — saving both Books and Magazines to the same file.
void saveLibrary(Repository<LibraryItem>& repo, std::string filename) { std::ofstream outFile(filename); for (const auto& item : repo.all()) { outFile << item->getTitle() << "|" << item->itemType() << "|" << item->isCheckedOut() << std::endl; } }
Genuinely the same CSV-style pattern from Phase 11 Module 5, just using | as the delimiter since titles might genuinely contain commas. Notice item->itemType() is called polymorphically (Phase 6 Module 5) — the SAME line of code correctly produces "Book by ..." or "Magazine, issue ..." depending on the object's real type.
itemType()'s text output loses the STRUCTURED author/issue-number data needed to rebuild a real Book or Magazine object — a genuinely real-world constraint of simple text formats. A production system would typically use a more structured format (like JSON) specifically to solve this. Recognizing a format's real limitations, rather than treating this simplified version as the final word, is itself part of genuine engineering judgment.🏛️ The Complete Reference Solution
Every piece from Modules 1 through 4, assembled into one genuinely working program.
#include <iostream> #include <vector> #include <memory> #include <algorithm> #include <fstream> #include <stdexcept> // (LibraryItem, Book, Magazine, Repository, and exception classes from Modules 2-4) int main() { Repository<LibraryItem> library; library.add(std::make_unique<Book>("The Death Road", "John Steve")); library.add(std::make_unique<Magazine>("Wired", 312)); try { checkOut(library, "The Death Road"); std::cout << "Checked out successfully." << std::endl; checkOut(library, "The Death Road"); // will throw — already checked out } catch (const ItemAlreadyCheckedOutError& e) { std::cout << "Error: " << e.what() << std::endl; } catch (const ItemNotFoundError& e) { std::cout << "Error: " << e.what() << std::endl; } for (const auto& item : library.all()) { std::cout << item->getTitle() << " — " << item->itemType() << (item->isCheckedOut() ? " [OUT]" : " [IN]") << std::endl; } saveLibrary(library, "library.txt"); std::cout << "Library saved. Total items: " << library.size() << std::endl; return 0; }
Run through this: Repository<LibraryItem> (Phase 9 templates), std::make_unique (Phase 8 smart pointers) creating Book/Magazine objects (Phase 6 inheritance), try/multiple catch blocks (Phase 10) around a function that validates before throwing (Phase 5's encapsulation principle), a range-based for loop (Phase 7) calling a virtual function polymorphically (Phase 6), and finally saveLibrary (Phase 11). Thirteen phases, one working program.
💼 Interview Prep — Memory & OOP
Genuine, commonly asked C++ interview questions — you've already learned every answer across this roadmap. This module just gathers them into interview-ready form.
A genuine answer: A pointer can be reassigned to point elsewhere, can be nullptr, and requires explicit dereferencing with *. A reference (Phase 3 Module 7) must be initialized when declared, can never be reassigned to refer to something else, and is used with the same syntax as the original variable. References are generally safer for simple pass-by-reference; pointers are needed when "no valid target" (nullptr) or reassignment is genuinely required.
A genuine answer: Memory allocated with new (Phase 4) that's never freed with delete, because the pointer to it was lost. Modern C++ prevents this structurally with RAII and smart pointers (Phase 8) — std::unique_ptr/std::shared_ptr guarantee cleanup happens automatically via the destructor, even during an early return or an exception (Phase 10 Module 4).
A genuine answer: Calling the same function on objects of different derived types and getting each type's own correct behavior, decided at runtime based on the object's ACTUAL type rather than a pointer's declared type (Phase 6 Module 5). This capstone's item->itemType(), called identically on both Book and Magazine objects through a shared LibraryItem*, is a genuine, concrete working example.
A genuine answer (a concept this roadmap touched on without naming formally): If a class manually manages a resource (like raw memory), and needs a custom destructor, it typically also needs a custom copy constructor and copy assignment operator — the "Rule of Three." Modern C++ extends this to "Rule of Five" adding move constructor and move assignment (Phase 8 Modules 5-6). Using smart pointers (Phase 8) instead of raw resource management is exactly what lets most classes AVOID needing to write any of these by hand at all.
💼 Interview Prep — STL & Modern C++
More genuine, commonly asked questions — this time focused on the tools from Phases 7 through 12.
A genuine answer: std::vector (Phase 7 Module 2) for ordered data accessed by position, where insertion order matters. std::map (Phase 7 Module 4) when you need to look things up by a meaningful key rather than a numeric position, and want that lookup to be efficient. Choosing between them is genuinely about how the data will actually be ACCESSED, not just what it stores.
A genuine answer: unique_ptr (Phase 8 Module 2) enforces single, exclusive ownership — it can't be copied, only moved. shared_ptr (Phase 8 Module 3) allows multiple owners via reference counting, deleting the resource only once every owner is gone. Default to unique_ptr unless genuine shared ownership is required — it's simpler and has less overhead.
A genuine answer: It tells the compiler to infer a variable's type from what's assigned to it, at COMPILE time (Phase 8 Module 7) — not a dynamic or runtime-flexible type the way it might work in some other languages. It's genuinely most useful for long, awkward types like STL iterators, and should be used carefully where it might otherwise hurt readability.
A genuine answer, drawing directly on Phase 12: Reproduce it reliably first. Use a real debugger (Module 2) to find the exact line and inspect the call stack at the crash point. Check for the classic causes from this roadmap: a null pointer dereference (Phase 4 Module 9), a dangling pointer after delete (Phase 4 Module 8), or an out-of-bounds array access (Phase 3 Module 2). A static analysis tool (Phase 12 Module 5) can also catch many of these before the program ever runs at all.
🎓 You Did It — What's Next
Fourteen phases. From "what is a program" to a genuinely working, polymorphic, exception-safe, file-persisting C++ system with its own test coverage and build system. This is the actual finish line.
Pre-Course → Fundamentals → Loops → Arrays/Strings/Functions → Pointers & Memory → OOP → Inheritance & Polymorphism → the STL → Modern C++ → Templates → Error Handling → File I/O → Tooling → this capstone. Every single one of those built directly on the last — and this final phase genuinely proved it, by using every one of them together in one real system.
That's not a vague encouragement — it's a direct claim you can now verify for yourself: you can explain memory management, RAII, and polymorphism in interview terms (Modules 7-8); you can build and debug a real multi-file project with CMake, git, and a debugger (Phase 12); and you've built something genuinely more substantial than a single-file script, with real error handling and persistence.
| Direction | What It Builds On |
|---|---|
| Concurrency & multithreading | Builds directly on this roadmap's memory-management foundation (Phase 4, Phase 8) |
| Game engines & graphics programming | Heavy on Phase 6's polymorphism and Phase 9's templates |
| Embedded & systems programming | Genuinely close to Phase 4's raw pointer/memory work, at an even lower level |
| Contributing to real open-source C++ projects | Directly exercises everything in Phase 12's tooling module |
Extend the Library System, For Real
Add a third item type (DVD, AudioBook — your choice), add a loadLibrary function mirroring saveLibrary, and set the whole project up with CMake, git, and one real unit test — genuinely applying Phase 12's exact capstone workflow to this phase's own project. Do this, and you will have built, end to end, a real, working, tested, version-controlled C++ application, entirely from what you learned in this roadmap.