I came across an example of basic std::thread
usage from "The C++ programming language" by Bjarne Stroustrup, and it confuses me. Here is the example:
void run(int i, int n) // warning: really poor code
{
thread t1 {f};
thread t2;
vector<Foo> v;
// ...
if (i<n)
{
thread t3 {g};
// ...
t2 = move(t3); // move t3 to outer scope
}
v[i] = Foo{}; // might throw
// ...
t1.join();
t2.join();
}
As Stroustrup writes:
we may never reach the two
join()
s at the end. In that case, the destructor fort1
will terminate the program.
But, in which case is t1
's destructor called? The t1
thread is not going out of its scope. There is no explicit delete
call, either.
I tried to run this code with appropriate changes, but still I couldn't find how t1
's destructor is being called.
But, in which case is
t1
's destructor called? Thet1
thread is not going out of its scope. There is no explicitdelete
call, either.
The code specifically says that the line v[i] = Foo{};
may throw an exception. If that happens, control never reaches the line t1.join();
afterwards in the same block scope, but all local variables of the scope, including t1
, are still destroyed and their destructors called.
When the destructor of std::thread
is called while the thread is neither joined nor detached, then the destructor will call std::terminate
which terminates the whole program by default, via a call to std::abort
.
More generally, the code is broken unless you can be sure that nothing before the .join
calls will throw an exception. If you can't be sure of that, then you must wrap it in try { /*...*/ } catch(...) { t1.join(); throw; }
. Technically, you need to do the same for t2
separately as well, since t1.join()
is also allowed to throw, although that might be a situation in which your program will have major problems to continue execution anyway.
With C++20, you can use std::jthread
to avoid such problems.