One of the problems with using std::thread
as an unadorned local variable is that it is not exception safe. I will admit that I am often guilty of this myself when demonstrating small little HelloWorlds.
However it is good to know exactly what you're getting into, so here is a more detailed explanation of the exception safety aspects of using std::thread
:
#include <iostream>
#include <thread>
void f() {}
void g() {throw 1;}
int
main()
{
try
{
std::thread t1{f};
g();
t1.join();
}
catch (...)
{
std::cout << "unexpected exception caught\n";
}
}
In the above example, I have a "large" program, which "occasionally" throws an exception. Typically I want to catch and handle exceptions before they bubble up to main
. However as a last resort, main
itself is wrapped up in a try-catch-all. In this example I simply print out that something really bad has happened and quit. In a more realistic example you might give your client a chance to save work, or free up memory or disk space, launch a different process that files a bug report, etc.
Looks good, right? Unfortunately wrong. When you run this, the output is:
libc++abi.dylib: terminating
Abort trap: 6
I didn't give my client the notification that something went wrong before returning from main
normally. I was expecting this output:
unexpected exception caught
Instead std::terminate()
got called.
Why?
As it turns out, ~thread()
looks like this:
thread::~thread()
{
if (joinable())
terminate();
}
So when g()
throws, t1.~thread()
runs during stack unwinding, and without t1.join()
getting called. Thus t1.~thread()
calls std::terminate()
.
Don't ask me why. It is a long story, and I lack the objectivity to tell it unbiasedly.
Regardless, you have to know about this behavior, and guard against it.
One possible solution is to go back to the wrapper design, perhaps using private inheritance as first proposed by the OP and warned against in other answers:
class CHandler
: private std::thread
{
public:
using std::thread::thread;
CHandler() = default;
CHandler(CHandler&&) = default;
CHandler& operator=(CHandler&&) = default;
~CHandler()
{
if (joinable())
join(); // or detach() if you prefer
}
CHandler(std::thread t) : std::thread(std::move(t)) {}
using std::thread::join;
using std::thread::detach;
using std::thread::joinable;
using std::thread::get_id;
using std::thread::hardware_concurrency;
void swap(CHandler& x) {std::thread::swap(x);}
};
inline void swap(CHandler& x, CHandler& y) {x.swap(y);}
The intent is to create a new type, say CHandler
that behaves just like a std::thread
, except for its destructor. ~CHandler()
should call either join()
or detach()
in its destructor. I've chosen join()
above. Now one can simply substitute CHandler
for std::thread
in my example code:
int
main()
{
try
{
CHandler t1{f};
g();
t1.join();
}
catch (...)
{
std::cout << "unexpected exception caught\n";
}
}
and the output is now:
unexpected exception caught
as intended.
Why choose join()
instead of detach()
in ~CHandler()
?
If you use join()
, then the stack unwinding of the main thread will block until f()
completes. This may be what you want, or it may not be. I can not answer this question for you. Only you can decide this design issue for your application. Consider:
// simulate a long running thread
void f() {std::this_thread::sleep_for(std::chrono::minutes(10));}
The main()
thread will still throw an exception under g()
, but now it will hang during unwinding, and only 10 minutes later print out:
unexpected exception caught
and exit. Perhaps because of references or resources that are used within f()
, this is what you need to have happen. But if it is not, then you can instead:
~CHandler()
{
if (joinable())
detach();
}
and then your program will immediately output "unexpected exception caught" and return, even though f()
is still busy crunching away (after main()
returns f()
will be forcefully canceled as part of a normal shutdown of the application).
Perhaps you need join()-on-unwinding
for some of your threads and detach()-on-unwinding
for others. Perhaps this leads you to two CHandler
-like wrappers, or to a policy-based wrapper. The committee was unable to form a consensus for a solution, and so you must decide what is right for you, or live with terminate()
.
This makes direct use of std::thread
very, very low-level behavior. Ok for Hello World, but in a real application, best encapsulated away in a mid-level handler, either via private inheritance or as a private data member. The good news is that in C++11 that mid-level handler can now be written portably (on top of std::thread
) instead of writing down to the OS or a 3rd-party lib as is necessary in C++98/03.