Vittorio Romeo Vittorio Romeo - 1 month ago 7
C++ Question

Does joining a member thread accessing other members of its parent class in the parent's destructor result in undefined behavior?

One of my coworkers claims that as soon as an object's destructor invocation begins, all accesses to the object's members done by a thread (that's a member of the object itself) are UB.

This implies that calling

std::thread::join
during the destructor of an object is UB if the thread is accessing any of the object's other members.

I briefly looked in the latest standard draft, under "Object Lifetime", but couldn't find something that gave me a conclusive answer.

Does the following code (on wandbox) introduce undefined behavior? What's the part of the standard that clarifies this interaction?

struct A
{
atomic<bool> x{true};
thread t;

// Capturing 'this' is part of the issue.
// The idea is that accessing 'this->x' becomes invalid as soon as '~A()' is entered.
// vvvv
A() : t([this]
{
while(x)
{
this_thread::sleep_for(chrono::milliseconds(100));
}
})
{
}

~A()
{
x = false;
t.join();
}
};

int main()
{
A a;
}

Answer

This is not undefined behavior. If we look at [class.dtor]/8 we have

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes.

which states the the non-static members of the class are destroyed after the body of the destructor is ran. That means all the members are alive in the destructor and manipulating x and calling join behave just like they would in a normal member function. The only difference is after the body of the destructor is ran then the members themselves will be destroyed.

Comments