joining_thread is a lightweight RAII wrapper around std::thread designed to ensure a safe lifecycle for execution threads.
The standard std::thread has one unpleasant property: if a thread object goes out of scope while still joinable, and neither join() nor detach() has been called explicitly, the program immediately terminates via std::terminate(). This often leads to hard-to-debug crashes during exception handling or early function exits.
joining_thread implements the RAII (Resource Acquisition Is Initialization) idiom. It takes responsibility for thread completion:
- Automatic join: The destructor checks the thread state and calls
join()if needed - Safe move semantics: Correctly handles move assignment by joining the old thread before taking ownership of a new one
- Interface compatibility: Uses variadic templates and perfect forwarding, allowing threads to be launched just as easily as with
std::thread
#include "joining_thread.hpp"
#include <iostream>
void task(int seconds) {
std::this_thread::sleep_for(std::chrono::seconds(seconds));
std::cout << "Task complete!" << std::endl;
}
int main() {
{
joining_thread t(task, 2);
// No need to call t.join()!
// The thread will be safely joined when leaving this scope.
}
return 0;
}Unlike naive implementations, joining_thread protects against std::terminate during move assignment:
joining_thread t1(worker, 1);
joining_thread t2(worker, 2);
t1 = std::move(t2); // t1 automatically joins its previous thread
// before taking ownership of t2's threadThe class provides access to the underlying thread via .get() and implements key parts of the std::thread interface (joinable(), join(), swap()), making it a drop-in replacement in most scenarios.
With the release of C++20, the standard introduced std::jthread. Our joining_thread conceptually mirrors its behavior but differs in several aspects:
| Feature | joining_thread (Custom) |
std::jthread (C++20) |
|---|---|---|
| Auto-join in destructor | ✅ Yes | ✅ Yes |
| Cooperative cancellation | ❌ No (manual implementation required) | ✅ Yes (via std::stop_token) |
| Compatibility | C++11 and later | C++20 only |
| Dependencies | <thread> only |
<thread> + <stop_token> |
Why use joining_thread if C++20 is available?
- Legacy projects — Your codebase is limited to C++11/14/17
- Minimalism — You need guaranteed joining but not the heavier
stop_tokenmechanism - Control — You want full control over wrapper behavior (e.g., logging in the destructor)
This code is distributed under the MIT License. Use it for the benefit of robust and stable software.