Before you can read the parent note, you must own every word it leans on. This page builds each one from zero — plain words first, then a picture, then why the topic needs it. Read top to bottom; each item is a brick for the next. The two named ends above are just labels for now; sections 3–6 build the machinery inside the box, and section 7 defines the ends precisely.
Picture two runners on parallel tracks. They start together, run at their own pace, and neither waits for the other unless you tell them to.
Figure 1 — the two arrows are the two threads (teal = main, orange = worker); the plum dots mark that both start at the same moment and then advance independently along their own track.
Why the topic needs it: promise/future exists only because more than one thread is running. If there were one thread, you'd just call a function and read its return value — no mailbox needed. See std::thread.
Picture a talking stick: only the person holding it may speak; everyone else waits.
std::mutex m; // the lock object itselfint shared_value; // the thing we protect{ std::lock_guard<std::mutex> lk(m); // lock m here (lk "guards" m) shared_value = 42; // safe: we hold the stick} // lk destroyed -> unlock automatically
Why the topic needs it: the shared state's flag and value are written by one thread and read by another — a mutex serialises those touches so there is no data race, and unique_lock is the exact tool the waiting step needs. See std::mutex and lock_guard.
Picture a mailbox with a little red flag: the postman raises it once the letter is inside, so you know to walk out and collect.
Why the topic needs it: without it, the waiter cannot tell an empty box from a box holding a legitimately-zero value. The flag is the "data present" bit — and it is the condition the next tool (the condition variable) waits on.
Picture someone napping with a note: "wake me when the kettle boils." They burn no energy waiting; whoever boils the kettle taps their shoulder — that tap is notify_one.
Figure 2 — the teal box is the sleeping waiter holding a unique_lock; the orange box is the setter that has just stored the value and flipped ready; the plum arrow is the notify_one() call that wakes the waiter. Compare with the busy-wait note at the top: that would spin the teal box at 100% CPU instead of sleeping.
The alternative — a busy-wait loop while(!ready) {} (using the ready flag from section 4) — spins the CPU at 100% doing nothing. The condition variable is the efficient sleep.
Here is the canonical wait loop the setter and waiter perform, using the m, ready, and cv from the shared state:
std::mutex m;std::condition_variable cv;bool ready = false;int value;// --- SETTER (producer thread) ---{ std::lock_guard<std::mutex> lk(m); // 1. lock the mutex value = 42; // 2. store the value ready = true; // 3. flip the ready flag} // 4. unlock (lk destroyed)cv.notify_one(); // 5. wake the waiter (outside the lock)// --- WAITER (consumer thread) ---{ std::unique_lock<std::mutex> lk(m); // unique_lock, NOT lock_guard cv.wait(lk, []{ return ready; }); // sleeps; releases m while asleep, // re-checks ready on every wake int got = value; // safe: we hold m and ready==true}
Picture one physical mailbox with two keys — the writer's key and the reader's key — but only one box.
Figure 3 — the central boxed block is the shared state; inside it are the four members from section 3–5 (value-or-exception, ready flag, mutex, condition variable). The orange arrow is the promise writing in; the teal arrow is the future reading out. Both arrows point at the same box — that is what "shared" means.
Why the topic needs it: this is literally the "one thing they jointly manage" the parent note names. Promise = writing end, future = reading end, shared state = the box between them.
Picture the mailbox again: the promise is the slot on the street where the postman drops the letter; the future is the little door on your side of the wall where you collect it.
Figure 4 — top row: copying a promise (the dashed clone, struck through in red) would make two writing ends aim at the same box, so two threads could both call set_value — the language forbids it. Bottom row: std::move transfers the single shared state from the old promise (now empty, dotted outline) to the new one (solid) — still exactly one owner. The picture is the rule: one box, one writer.
Why the topic needs it: std::thread t(worker, std::move(prom)); — you hand the promise to the worker; cloning it would be an error the language refuses to allow. See Move semantics and std::move.
Picture opening a sealed envelope: once opened and the letter removed, there's nothing left to open again. A second get() throws future_error with code no_state.
Why the topic needs it: it explains why get() invalidates the future, why valid() flips to false, and why multiple readers need std::shared_future instead.
Once these foundations are solid, the parent topic connects outward to higher-level tools that wrap the same machinery: std::async and std::launch (which hands you a future automatically), std::packaged_task (which wraps a callable's return in a promise), and std::shared_future (many readers of one result).
Test yourself — you should be able to answer each before reading the parent note.
What is a thread, in one line?
An independent stream of instructions that runs at the same time as others.
What is a data race and why is it dangerous?
Two threads touch the same memory with at least one writing — it is undefined behaviour (garbage, crash, or hidden bugs).
What does a mutex guarantee?
Only one thread at a time enters the protected region; others wait, so shared access is serialised and safe.
What does lock_guard do for you?
Locks the mutex on creation and unlocks it automatically at end of scope, so you never forget to release.
Why does condition_variable::wait need unique_lock rather than lock_guard?
Because wait must release the mutex while sleeping and re-acquire it on waking, which only unique_lock allows; lock_guard cannot unlock partway through and won't compile there.
What job does the ready flag do?
It records whether the value has been placed in the box, so the reader can tell an empty box from a filled one.
What are notify_one and notify_all?
Condition-variable calls that wake one waiting thread (notify_one) or every waiting thread (notify_all) after the condition changed.
In what order must the setter act, and where does notify go?
Lock the mutex, store the value, flip the ready flag, unlock, then call notify_one outside the lock (notifying while locked just makes the woken thread block again).
Why must a condition-variable wait re-check its condition in a loop?
Because of spurious wakeups (waking with no notify) and the case where the value was set before waiting began — wait(lk, pred) is exactly that re-checking loop.
Why is the handoff actually safe (memory-ordering)?
Unlocking a mutex happens-before the next lock of the same mutex, so all writes made before unlock (value and ready) are visible after the waiter re-locks inside wait.
Why use a condition variable instead of a busy-wait loop?
It lets the waiting thread sleep at zero CPU cost until it is signalled, instead of spinning uselessly.
What is the shared state?
A heap-allocated control block holding the value-or-exception, ready flag, mutex, and condition variable, shared by promise and future.
What is a std::promise and what is a std::future?
The promise is the writing end (set_value or set_exception once); the future is the reading end (get() blocks then reads once).
What is std::future_error?
The exception type (from , derived from logic_error) thrown on future misuse; it carries a future_errc code like broken_promise, future_already_retrieved, or no_state.
What happens if a promise is destroyed without being set?
The future's get() throws a std::future_error with code broken_promise.
How does an exception cross a thread boundary?
Capture it as a std::exception_ptr with current_exception(), store it, then rethrow_exception in the other thread.
Why is std::promise move-only?
A shared state must have exactly one writer; copying would create two writing ends to one box.
What does one-shot mean here?
The promise is set once and the future is read once; get() moves the value out and invalidates the future.
When would you use std::shared_future instead of std::future?
When several threads must all read the same result, since shared_future is copyable and its get() can be called many times.