1

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 for t1 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.

3
  • 1
    When run returns the local variables get out of scope and are destroyed (and the destructor for t1 etc. is called). Commented Apr 6, 2023 at 4:28
  • 1
    Other than an infinite loop (in which case run() will never return, and locally constructed objects never destroyed) the only things that can happen to cause the join()s to be never reached are an exception being thrown (possibly by a called function) or a return statement in preceding code within run(). In both cases, all locally constructed objects (of automatic storage duration) are destroyed, in reverse order of their construction, and their destructors called/invoked. Since t1 is constructed before t2, it is destructed last. Commented Apr 6, 2023 at 4:47
  • Example Commented Apr 6, 2023 at 4:52

1 Answer 1

5

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.

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.