Catch a thread's exception in the caller thread?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
An exception thrown inside a worker thread does not jump across to the thread that launched it. Exception handling follows call stacks, and each thread has its own stack. If you want the caller thread to observe a worker failure, you need to capture the exception in the worker and transport it explicitly.
Why a Caller-Side try Block Is Not Enough
This is the core misconception:
The catch block is on the caller thread's stack, not the worker's stack. An uncaught exception in a raw std::thread typically ends the program through std::terminate.
So the correct model is:
- catch inside the worker
- store the failure somewhere shared
- rethrow or inspect it after
join
Use std::exception_ptr with Raw Threads
The standard transport type in C++ is std::exception_ptr.
This is the direct answer when you are already using std::thread.
std::async and std::future Are Often Cleaner
If the work is really "run this task and give me a result later", std::async is usually simpler because exception propagation is built in. The future stores the exception and rethrows it on get().
That is often preferable to hand-rolling exception transport unless you specifically need raw thread control.
Multiple Threads Need a Policy
Once you have more than one worker, you need to choose an error policy:
- stop on the first failure
- collect all failures
- convert failures into result objects
Here is a simple aggregation pattern:
The synchronization matters because multiple workers may fail at the same time.
Design Around Tasks, Not Just Threads
A lot of code becomes simpler when you stop thinking "how do I catch a thread's exception?" and start thinking "how does this asynchronous task report success or failure?" Futures, promises, task executors, or message queues often express that design better than raw thread objects do.
The more structured the async abstraction, the less custom exception plumbing you usually need.
Common Pitfalls
- Expecting a caller-side
tryblock to catch an exception thrown on a worker thread. - Letting an exception escape a raw
std::thread, which often leads to termination. - Checking shared exception state before joining the thread.
- Writing to one shared
exception_ptrfrom multiple workers without synchronization. - Using raw threads when a future-based task model would handle propagation automatically.
Summary
- Exceptions do not cross thread boundaries automatically.
- With raw threads, catch inside the worker and transport with
std::exception_ptr. - With
std::async, exceptions are propagated through thefuture. - Multiple workers require an explicit failure policy and synchronization.
- If your real problem is task failure reporting, a higher-level async abstraction is often the better design.

