Learn Advanced C++: From Concurrency to Coroutines
Goal: Build the mental models and implementation skills required for modern, high-performance C++. You will understand how C++ handles failure (exceptions + RAII), how to safely share state across threads (synchronization + memory model), how to push correctness into the type system (templates + TMP), and how to structure asynchronous systems with C++20 coroutines. By the end, you will be able to design exception-safe containers, concurrent data structures, compile-time enforced APIs, and coroutine-based network clients with confidence.
Introduction
Advanced C++ is the set of language features and design techniques that turn a correct program into a robust, fast, and maintainable system. It exists to solve the problems you run into when software meets the real world: memory pressure, concurrency, latency, large codebases, and complex invariants.
In this guide you will build:
- An exception-safe vector (strong guarantee under allocation failure)
- A thread-safe producer/consumer queue (no busy waiting, clean shutdown)
- A compile-time unit system (type-level dimension checking)
- A coroutine-based Redis client (clean async I/O)
Scope: exception safety, RAII, concurrency primitives, the C++ memory model, template metaprogramming, and C++20 coroutines with asynchronous I/O. Out of scope: GUI frameworks, full networking stacks, and OS kernel development.
Big picture (what the topics connect to):
Failures + Ownership
(exceptions + RAII + noexcept)
|
v
Safe State Under Mutation (Containers)
|
v
Shared State (threads, mutexes, condition vars)
|
v
Memory Visibility (atomics, happens-before)
|
v
Compile-Time Guarantees (templates, TMP, units)
|
v
Async Systems (coroutines + event loops + protocols)
How to Use This Guide
- Read the theory primer first. It is a mini-book; treat each chapter as a short textbook section.
- Pick a project based on your goals (exception safety, concurrency, TMP, or async).
- Before coding: answer the design questions and complete the thinking exercise.
- Implement in phases, using the hints only if you get stuck.
- Validate using the Definition of Done and the testing guidance.
- Reflect by answering the interview questions and updating your notes.
Prerequisites & Background
Essential Prerequisites (Must Have)
- Solid C++ fundamentals: classes, templates, references, RAII basics
- Memory model basics: stack vs heap, object lifetimes
- Familiarity with the standard library (
std::vector,std::unique_ptr) - Basic compilation workflow (CMake or Make)
Helpful But Not Required
- Move semantics and perfect forwarding
- Familiarity with
std::thread,std::mutex - Basic networking (TCP client/server concept)
Self-Assessment Questions
- Can you explain when copy vs move constructors are called?
- Can you describe what happens during stack unwinding?
- What is a data race in C++ and why is it undefined behavior?
- Why must condition variables always use a predicate?
- What is SFINAE and how does it prevent compilation errors?
- What does
co_awaitexpand to conceptually?
Development Environment Setup
- Compiler: GCC 13+ or Clang 16+ with C++20 support
- Build: CMake 3.20+ or Make
- Debug: lldb or gdb
- Sanitizers: ASan/UBSan/TSan (
-fsanitize=address,undefined,thread) - Redis: Local Redis server (for Project 4)
Time Investment
- Project 1: 1-2 weeks
- Project 2: 1-2 weeks
- Project 3: 2-3 weeks
- Project 4: 2-4 weeks
Important Reality Check
These projects are intentionally difficult. Expect to read compiler errors carefully, build small experiments, and revisit your design more than once. That is the whole point.
Big Picture / Mental Model
C++ SYSTEMS SAFETY STACK
[Resource Ownership]
|
v
[Exception Guarantees] ---> [Container Invariants]
|
v
[Mutexes + CondVars] ---> [Safe Shared State]
|
v
[Atomics + Memory Order] ---> [Visibility + Ordering]
|
v
[Templates + TMP] ---> [Compile-Time Constraints]
|
v
[Coroutines + Async I/O] ---> [High-Throughput Services]
Theory Primer (Mini-Book)
Chapter 1: Exception Safety and RAII
Definitions & Key Terms
- Exception safety: guarantees about program state if an exception is thrown.
- RAII: bind resource lifetime to object lifetime (acquire in constructor, release in destructor).
- Stack unwinding: automatic destruction of objects during exception propagation.
Mental Model Diagram (ASCII)
throw --> unwind stack --> destructors run --> invariants restored
[Function A]
[ResourceGuard g] <--- destructor runs during unwind
[Function B]
throw
How It Works (Step-by-Step)
- A function throws an exception.
- The runtime begins stack unwinding.
- Destructors for in-scope objects run in reverse order.
- Resources are released deterministically via RAII.
- The exception propagates to the nearest handler.
Minimal Concrete Example
struct FileGuard {
std::FILE* f;
explicit FileGuard(const char* path) : f(std::fopen(path, "r")) {
if (!f) throw std::runtime_error("open failed");
}
~FileGuard() { if (f) std::fclose(f); }
};
Common Misconceptions
- “RAII means no exceptions” -> RAII exists to make exceptions safe.
- “Destructors can throw” -> they must be
noexceptor you risk terminate.
Check-Your-Understanding Questions
- Why does RAII make strong exception safety possible?
- What happens if a destructor throws during stack unwinding?
- How does RAII differ from finally blocks in other languages?
Where You’ll Apply It
- Project 1: Exception-Safe Vector
- Project 2: Thread-Safe Queue (lock guards)
Chapter 2: Exception Guarantees and noexcept
Definitions & Key Terms
- Basic guarantee: no leaks; object remains valid.
- Strong guarantee: commit-or-rollback semantics.
- No-throw guarantee: operation never throws.
noexcept: compiler-visible guarantee for optimization and correctness.
Mental Model Diagram (ASCII)
Basic: State may change, but valid
Strong: Either success OR no change
No-throw: Always success
How It Works (Step-by-Step)
- Identify operations that can throw (allocation, copying, user code).
- Use temporary objects to build new state.
- Commit by swap only after success.
- Mark swap/destructors
noexcept.
Minimal Concrete Example
T& operator=(T other) noexcept { // copy or move then swap
swap(other);
return *this;
}
Common Misconceptions
- “
noexceptmakes code faster” -> only when it enables move or devirtualization. - “Strong guarantee is always possible” -> not if invariant depends on external state.
Check-Your-Understanding Questions
- Why does
std::vectorsometimes prefer copy over move? - Which operations must be
noexceptfor strong guarantees?
Where You’ll Apply It
- Project 1: push_back strong guarantee
Chapter 3: Concurrency Primitives and Coordination
Definitions & Key Terms
- Data race: two threads access the same location, at least one write, no synchronization.
- Mutex: mutual exclusion lock to protect shared data.
- Condition variable: thread waits until a predicate becomes true. It can wake spuriously, so always re-check.
Mental Model Diagram (ASCII)
Producer Thread Consumer Thread
lock(m) lock(m)
push(item) wait(cv, pred)
unlock(m) notify_one ---> unlock(m)
How It Works (Step-by-Step)
- Producers lock the mutex, push data, unlock.
- Producers notify waiting consumers.
- Consumers wait on a condition variable with a predicate.
- On wake, consumers re-check the predicate and pop items safely.
Minimal Concrete Example
cv.wait(lock, [&]{ return !q.empty() || shutdown; });
Common Misconceptions
- “Notify means data is ready” -> spurious wakeups require re-check.
- “Lock-free means faster” -> often slower if contention is low.
Check-Your-Understanding Questions
- Why must condition variables use a predicate?
- What is the difference between
notify_oneandnotify_all?
Where You’ll Apply It
- Project 2: Thread-Safe Producer/Consumer Queue
Chapter 4: The C++ Memory Model and Atomics
Definitions & Key Terms
- Happens-before: partial order that defines visibility between operations.
- Atomic operation: indivisible read/modify/write with ordering semantics.
- Memory order:
relaxed,acquire,release,acq_rel,seq_cst.
Mental Model Diagram (ASCII)
Thread A Thread B
store(x=1, release) ---> load(x, acquire)
(writes before) (sees writes after)
How It Works (Step-by-Step)
- A release store makes prior writes visible.
- An acquire load observes those writes.
seq_cstenforces a single global order.
Minimal Concrete Example
std::atomic<int> flag{0};
// writer
flag.store(1, std::memory_order_release);
// reader
if (flag.load(std::memory_order_acquire) == 1) {
// safe to read data protected by release/acquire
}
Common Misconceptions
- “Atomic means thread-safe for all data” -> only for that specific variable.
- “
memory_order_relaxedis always safe” -> only for counters or independent stats.
Check-Your-Understanding Questions
- Why are data races undefined behavior in C++?
- When is
seq_cstrequired vsacquire/release?
Where You’ll Apply It
- Project 2: shutdown flags, counters
- Project 4: coroutine scheduling state
Chapter 5: Templates and Compile-Time Programming
Definitions & Key Terms
- Template instantiation: compiler generates concrete code from templates.
- SFINAE: substitution failure is not an error.
- Type traits: compile-time queries about types.
std::ratio: compile-time rational numbers.
Mental Model Diagram (ASCII)
Source Template ----> Instantiation ----> Generated Type/Function
How It Works (Step-by-Step)
- The compiler substitutes template parameters.
- If substitution fails, the candidate is removed (SFINAE).
- The best viable overload is chosen.
Minimal Concrete Example
template<typename T>
std::enable_if_t<std::is_integral_v<T>, T>
add_one(T v) { return v + 1; }
Common Misconceptions
- “Templates are just macros” -> templates participate in type checking.
- “SFINAE hides all errors” -> only during substitution, not in the body.
Check-Your-Understanding Questions
- What does
if constexprchange about template errors? - Why is
std::ratiouseful for unit systems?
Where You’ll Apply It
- Project 3: Compile-Time Units
Chapter 6: Type-Level Units and Dimensional Analysis
Definitions & Key Terms
- Dimension vector: type-level exponents for base units.
- Dimensional analysis: validating physical equations by unit consistency.
- Zero-cost abstraction: type safety with no runtime overhead.
Mental Model Diagram (ASCII)
Meters: (L=1, M=0, T=0)
Seconds: (L=0, M=0, T=1)
Velocity: (L=1, M=0, T=-1)
How It Works (Step-by-Step)
- Encode base units as type parameters.
- Operator overloads add/subtract exponents at compile time.
- Incompatible units fail to compile.
Minimal Concrete Example
template<int M, int L, int T> struct Dim {};
using Meters = Dim<0,1,0>;
using Seconds = Dim<0,0,1>;
Common Misconceptions
- “This needs runtime checks” -> type-level checks happen at compile time.
- “Templates always bloat code” -> many get optimized away.
Check-Your-Understanding Questions
- Why does
meters + secondsfail to compile? - How do you represent derived units like Newtons?
Where You’ll Apply It
- Project 3: Compile-Time Units
Chapter 7: Coroutines and Async I/O
Definitions & Key Terms
- Coroutine: function that can suspend and resume at
co_awaitpoints. - Promise type: the object that controls coroutine behavior.
- Awaitable: type that defines
await_ready,await_suspend,await_resume. - Event loop: scheduler that runs pending async operations.
Mental Model Diagram (ASCII)
caller --> coroutine frame --> suspend
| (await_suspend)
v
resume when I/O completes
How It Works (Step-by-Step)
- The compiler transforms a coroutine into a state machine.
co_awaitcallsawait_ready/await_suspend.- The coroutine frame is suspended and stored.
- The I/O library resumes the coroutine when ready.
Minimal Concrete Example
task<int> fetch() {
co_await async_wait();
co_return 42;
}
Common Misconceptions
- “Coroutines are threads” -> they are stackless state machines.
- “
co_awaitblocks” -> it suspends without blocking the thread.
Check-Your-Understanding Questions
- Who owns the coroutine frame?
- What happens if an awaitable resumes immediately?
Where You’ll Apply It
- Project 4: Coroutine Redis Client
Chapter 8: Redis Protocol (RESP) and Client Design
Definitions & Key Terms
- RESP: Redis Serialization Protocol (RESP2/RESP3).
- Bulk string:
$<len>\r\n<data>\r\n - Array:
*<len>\r\nfollowed by elements.
Mental Model Diagram (ASCII)
Client Redis
*2\r\n (parse array)
$3\r\nGET\r\n ---> (execute GET)
$3\r\nkey\r\n
<--- $5\r\nvalue\r\n
How It Works (Step-by-Step)
- Client encodes commands into RESP arrays.
- Server parses the frames and executes commands.
- Server replies with a typed RESP response.
- Client parses and resumes awaiting coroutine.
Minimal Concrete Example
*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
Common Misconceptions
- “Redis is text-only” -> RESP is structured and binary-safe.
- “Parsing is trivial” -> you must handle partial reads and buffering.
Check-Your-Understanding Questions
- How do you handle partial frames from a TCP stream?
- Why is framing important for pipelining?
Where You’ll Apply It
- Project 4: Coroutine Redis Client
Glossary (High-Signal)
- RAII: C++ pattern tying resource lifetime to object lifetime.
- Strong exception guarantee: if an operation fails, state is unchanged.
- Data race: unsynchronized conflicting access to shared memory.
- Happens-before: relation that defines visibility between operations.
- Coroutine frame: compiler-generated storage for coroutine state.
- RESP: Redis serialization protocol used for commands and replies.
Why Advanced C++ Matters
- C++ remains a top-ranked systems language in major industry indexes (e.g., TIOBE December 2025 lists C++ in the top 3).
- Performance-critical systems (databases, trading, game engines) demand deterministic resource control and predictable latency.
- Modern C++ enables zero-cost abstractions: safety and speed together.
Context & Evolution: C++ evolved from manual error codes and synchronous designs to strong exception safety, fine-grained atomics, and coroutine-based async APIs. This guide focuses on the modern, safe side of that evolution.
Old Style Modern C++
----------- ----------------------------
manual cleanup RAII + smart pointers
busy-wait loops condition variables
runtime unit checks compile-time unit types
callback pyramids coroutines
Concept Summary Table
| Concept | What You Must Internalize | Why It Matters | Projects |
|---|---|---|---|
| Exception safety | basic/strong/no-throw guarantees | prevents corruption on failure | P01 |
| RAII | lifetime ties to resources | leak-free cleanup | P01, P02 |
| Mutex + condvar | safe shared state & coordination | avoids data races | P02 |
| Memory order | visibility + ordering of writes | correctness across cores | P02, P04 |
| TMP + type traits | compile-time constraints | safe APIs | P03 |
| Dimensional analysis | type-safe units | prevents logic errors | P03 |
| Coroutines | suspend/resume async flow | clean async I/O | P04 |
| RESP protocol | request framing, parsing | reliable Redis client | P04 |
Project-to-Concept Map
| Project | Concepts Applied |
|---|---|
| P01 Exception-Safe Vector | RAII, strong guarantee, noexcept |
| P02 Thread-Safe Queue | mutexes, condition variables, atomics, shutdown protocols |
| P03 Compile-Time Units | templates, TMP, std::ratio, static assertions |
| P04 Coroutine Redis Client | coroutines, awaitables, async I/O, RESP parsing |
Deep Dive Reading by Concept
| Concept | Book | Chapters / Sections |
|---|---|---|
| Exception Safety | The C++ Programming Language (Stroustrup) | Exceptions and Resource Management chapters |
| RAII | A Tour of C++ (Stroustrup) | Resource management + RAII sections |
| Concurrency | The C++ Programming Language (Stroustrup) | Concurrency and Memory Model chapters |
| Templates & TMP | The C++ Programming Language (Stroustrup) | Templates and Generic Programming chapters |
| Coroutines | A Tour of C++ (Stroustrup) | Coroutines overview section |
| Redis Protocol | TCP/IP Illustrated (Stevens) | TCP stream behavior + framing concepts |
(External references you may also want: Effective C++, C++ Concurrency in Action, C++ Templates: The Complete Guide)
Quick Start (First 48 Hours)
Day 1
- Read Chapters 1-3 (exceptions + concurrency).
- Build a minimal RAII guard and a mutex-protected counter.
- Install Redis locally and verify
redis-cli ping.
Day 2
- Read Chapters 4-8 (memory model, TMP, coroutines, RESP).
- Build a tiny TMP type trait and compile-time dimension struct.
- Write a coroutine that suspends and resumes via a timer.
Recommended Learning Paths
- Reliability Path: P01 -> P02
- Type-Safety Path: P01 -> P03
- Async Systems Path: P02 -> P04
- Full Mastery: P01 -> P02 -> P03 -> P04
Success Metrics
- You can explain exception guarantees with examples.
- Your concurrent queue never deadlocks and shuts down cleanly.
- You can intentionally trigger compile-time errors for invalid units.
- Your coroutine client performs real Redis commands without blocking a thread.
Appendix: Tooling and Debugging Cheatsheet
- ASan:
-fsanitize=address -fno-omit-frame-pointer - UBSan:
-fsanitize=undefined - TSan:
-fsanitize=thread - Valgrind:
valgrind --leak-check=full ./app - Thread issues: add logging around lock acquisition order
Projects
Project 1: The Exception-Safe Vector
- File: LEARN_ADVANCED_CPP_DEEP_DIVE.md
- Main Programming Language: C++
- Alternative Programming Languages: Rust (comparison of error handling)
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Exception Safety / RAII
- Software or Tool: C++17 compiler, ASan/Valgrind
- Main Book: “Effective C++” by Scott Meyers
What you’ll build: A simplified Vector<T> with strong exception guarantees on push_back and resize.
Why it teaches exception handling: you must guarantee that allocation or element copy failures do not corrupt the container state.
Real World Outcome
A deterministic test harness that injects allocation failures and proves the vector remains valid.
$ ./vector_test --fail-after=5 --seed=42
[TEST] push_back with failure injection
[ALLOC] countdown=5
[ALLOC] countdown=4
[ALLOC] countdown=3
[ALLOC] countdown=2
[ALLOC] countdown=1
[ALLOC] countdown=0 -> throwing std::bad_alloc
[OK] caught std::bad_alloc
[OK] size=5 capacity=8
[OK] elements intact: 0 1 2 3 4
[OK] no leaks detected (ASan)
The Core Question You’re Answering
“How do I guarantee that a container is never corrupted even when constructors or allocations fail?”
Concepts You Must Understand First
- RAII and stack unwinding (Stroustrup, C++PL: Exceptions and RAII)
- Strong vs basic exception guarantees (Effective C++)
noexceptand move semantics (C++PL: Move semantics)
Questions to Guide Your Design
- Where can exceptions be thrown inside
push_back? - How do you keep old state intact while allocating new storage?
- Which operations must be
noexceptfor strong guarantees?
Thinking Exercise
Draw the state of a vector before and after a failed reallocation. Which objects are alive? Which destructors must run?
The Interview Questions They’ll Ask
- What is the strong exception guarantee?
- Why should
swapbenoexcept? - How does copy-and-swap achieve rollback semantics?
- When does
std::vectorchoose copy vs move?
Hints in Layers
Hint 1: Use a temporary buffer
auto newbuf = std::unique_ptr<T[]>(new T[newcap]);
Hint 2: Copy/move into the temporary buffer
for (size_t i = 0; i < size_; ++i) newbuf[i] = data_[i];
Hint 3: Commit with swap
swap(data_, newbuf);
Books That Will Help
| Topic | Book | Chapter | |—|—|—| | Exception safety | Effective C++ | Items on exception safety | | RAII | A Tour of C++ | Resource Management | | Containers | The C++ Programming Language | Containers chapter |
Common Pitfalls & Debugging
Problem: “Vector size changed after bad_alloc”
- Why: you modified size before successful commit
- Fix: only update size after allocation + element construction
- Quick test: run allocation failure harness
Problem: “Double free”
- Why: ownership not transferred safely
- Fix: use
unique_ptruntil commit - Quick test: ASan run
Definition of Done
push_backprovides strong exception guarantee- No leaks under ASan/Valgrind
swap, destructor arenoexcept- Allocation failure leaves container valid
Project 2: A Thread-Safe Producer-Consumer Queue
- File: LEARN_ADVANCED_CPP_DEEP_DIVE.md
- Main Programming Language: C++
- Alternative Programming Languages: Java, Go
- Coolness Level: Level 3: Genuinely Clever
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 3: Advanced
- Knowledge Area: Concurrency / Multithreading
- Software or Tool:
std::thread,std::mutex,std::condition_variable - Main Book: “C++ Concurrency in Action” by Anthony Williams
What you’ll build: A multi-producer, multi-consumer queue with clean shutdown and zero busy-waiting.
Real World Outcome
A deterministic run with one producer and one consumer (fixed schedule) and a clean shutdown.
$ ./ts_queue_demo --items=5 --seed=7
[PRODUCER] push 0
[CONSUMER] pop 0
[PRODUCER] push 1
[CONSUMER] pop 1
[PRODUCER] push 2
[CONSUMER] pop 2
[PRODUCER] push 3
[CONSUMER] pop 3
[PRODUCER] push 4
[CONSUMER] pop 4
[QUEUE] shutdown
[CONSUMER] exit
The Core Question You’re Answering
“How can multiple threads safely share a queue without races or wasted CPU?”
Concepts You Must Understand First
- Mutex + lock guard (C++PL: Concurrency)
- Condition variables and spurious wakeups (C++ Concurrency in Action)
- Happens-before and data races (C++PL: Memory model)
Questions to Guide Your Design
- Which operations must hold the lock?
- What is the predicate for
cv.wait? - How do you guarantee all consumers exit on shutdown?
Thinking Exercise
Write a timeline of two threads: one producer, one consumer. Show all lock/unlock and notify/wait events.
The Interview Questions They’ll Ask
- Why do condition variables require a loop?
- What is a data race in C++?
- How do you avoid deadlocks in multi-lock code?
- What happens if notify occurs before wait?
Hints in Layers
Hint 1: Always wait with a predicate
cv.wait(lock, [&]{ return !q.empty() || shutdown; });
Hint 2: Use notify_all on shutdown
shutdown = true; cv.notify_all();
Hint 3: Return std::optional<T> for pop
if (shutdown && q.empty()) return std::nullopt;
Books That Will Help
| Topic | Book | Chapter | |—|—|—| | Condition variables | C++ Concurrency in Action | Ch. 4 | | Mutexes | The C++ Programming Language | Concurrency chapter | | Thread lifecycle | C++ Concurrency in Action | Ch. 2 |
Common Pitfalls & Debugging
Problem: “Consumer spins at 100% CPU”
- Why: missing condition variable or predicate
- Fix: use
cv.wait(lock, predicate) - Quick test: run idle with no producers
Problem: “Deadlock on shutdown”
- Why: notify without setting shutdown flag
- Fix: set flag then notify_all
- Quick test: run with zero producers
Definition of Done
- Multiple producers/consumers run without data races
- Idle consumers do not busy-wait
- Clean shutdown wakes all consumers
- TSan reports no races
Project 3: A Compile-Time Unit Conversion Library
- File: LEARN_ADVANCED_CPP_DEEP_DIVE.md
- Main Programming Language: C++
- Alternative Programming Languages: None (C++ specific)
- Coolness Level: Level 5: Pure Magic
- Business Potential: 1. The “Resume Gold”
- Difficulty: Level 4: Expert
- Knowledge Area: Template Metaprogramming
- Software or Tool: C++17 compiler
- Main Book: “C++ Templates” by Vandevoorde, Josuttis, Gregor
What you’ll build: A zero-runtime-cost unit system that rejects invalid arithmetic at compile time.
Real World Outcome
Successful compile for correct units, and a clear compile error for invalid units.
$ ./units_demo
10 m/s
$ ./build.sh
error: static assertion failed: Cannot add values with different dimensions
The Core Question You’re Answering
“How can the compiler prevent invalid physical equations before the program runs?”
Concepts You Must Understand First
- Template parameter packs / type traits (C++ Templates)
std::ratioand compile-time arithmetic (cppreference)if constexpr/static_assert(C++PL: Templates)
Questions to Guide Your Design
- How will you encode base dimensions?
- How will you compute new dimensions for multiply/divide?
- How will you restrict operator+ to identical dimensions?
Thinking Exercise
Define Force = Mass * Length / Time^2 in terms of dimension exponents. What is the exponent vector?
The Interview Questions They’ll Ask
- What is SFINAE and why is it useful?
- How does
if constexprchange template errors? - Why do unit systems reduce bugs in large codebases?
Hints in Layers
Hint 1: Encode dimensions as an int pack
template<int M, int L, int T> struct Dim {};
Hint 2: Add/subtract dimensions in helper types
template<typename D1, typename D2> struct AddDims;
Hint 3: Use std::is_same_v for operator+
static_assert(std::is_same_v<D1, D2>, "Cannot add...");
Books That Will Help
| Topic | Book | Chapter | |—|—|—| | TMP basics | C++ Templates | Early TMP chapters | | Generic programming | The C++ Programming Language | Templates chapter | | Type systems | A Tour of C++ | Generics section |
Common Pitfalls & Debugging
Problem: “Error messages are unreadable”
- Why: template errors are verbose
- Fix: use
static_assertwith clear messages - Quick test: intentionally compile invalid code
Definition of Done
- Correct units compile and run
- Invalid unit arithmetic fails to compile with readable error
- No runtime overhead for unit checks
Project 4: A Coroutine-based Redis Client
- File: LEARN_ADVANCED_CPP_DEEP_DIVE.md
- Main Programming Language: C++
- Alternative Programming Languages: Python, Rust (async/await comparison)
- Coolness Level: Level 5: Pure Magic
- Business Potential: 2. The “Micro-SaaS / Pro Tool”
- Difficulty: Level 5: Master
- Knowledge Area: Coroutines / Asynchronous Programming / Networking
- Software or Tool: C++20 compiler, Asio
- Main Book: “C++20 - The Complete Guide” by Nicolai M. Josuttis
What you’ll build: A coroutine-based Redis client using RESP framing and async I/O. The API should feel synchronous while remaining fully non-blocking.
Real World Outcome
A coroutine client that executes actual Redis commands.
$ ./redis_client_demo
[CONNECT] 127.0.0.1:6379
[CMD] SET user:1 alice
[RESP] +OK
[CMD] GET user:1
[RESP] $5 alice
[CMD] DEL user:1
[RESP] :1
The Core Question You’re Answering
“How do I turn callback-based network I/O into clean, linear code?”
Concepts You Must Understand First
- Coroutine promise types and handles (cppreference)
- Awaitable interface (cppreference)
- RESP framing and parsing (Redis docs)
Questions to Guide Your Design
- Who owns the coroutine frame?
- How do you resume the coroutine when data arrives?
- How do you parse partial RESP frames?
Thinking Exercise
Write a finite state machine for parsing RESP arrays from a TCP stream.
The Interview Questions They’ll Ask
- What is a coroutine frame and who destroys it?
- How do coroutines differ from threads?
- What is the reactor pattern?
- How do you avoid blocking in async code?
Hints in Layers
Hint 1: Start with a minimal task<T>
struct task { struct promise_type { /*...*/ }; };
Hint 2: Wrap async ops in awaitables
auto await_suspend(std::coroutine_handle<> h) { async_read(..., [h]{ h.resume(); }); }
Hint 3: Buffer RESP parsing
while (!frame_complete(buf)) co_await read_more();
Books That Will Help
| Topic | Book | Chapter | |—|—|—| | Coroutines | A Tour of C++ | Coroutines section | | Networking | TCP/IP Illustrated | TCP streams, framing | | Async design | The C++ Programming Language | Concurrency chapter |
Common Pitfalls & Debugging
Problem: “Coroutine never resumes”
- Why: awaitable never calls resume in callback
- Fix: ensure callback captures and resumes handle
- Quick test: add logging in await_suspend
Problem: “Protocol parsing fails under load”
- Why: partial reads not handled
- Fix: implement incremental parser with buffer
- Quick test: simulate partial chunks in tests
Definition of Done
- Client can connect, GET, SET, DEL
- All I/O is non-blocking
- Partial RESP frames parsed correctly
- Clean shutdown and error handling
Summary
| Project | Main C++ Topic | Difficulty | Focus |
|---|---|---|---|
| 1. Exception-Safe Vector | Exception Handling | Advanced | RAII, exception guarantees, noexcept |
| 2. Thread-Safe Queue | Concurrency | Advanced | std::thread, std::mutex, std::condition_variable |
| 3. Compile-Time Units | TMP | Expert | Type system manipulation, static_assert |
| 4. Coroutine Redis Client | Coroutines | Master | Awaitables, async I/O, RESP parsing |