Is it legal for a second thread to delete an object while a first thread is still potentially inside a member function of that object, if the deletion occurs after the last (explicit) field access inside that function?
This will probably be clearer with an example:
#include <thread>
#include <atomic>
struct handoff {
std::atomic<int> flag{};
void signal() { flag = 1; }
};
int main() {
auto h = new handoff;
auto t2 = std::thread([=]{
while (!h->flag) {
}
delete h;
});
h->signal();
t2.join();
}
Here, the handoff h object is used to communicate between T1 (the main thread) and T2. The flag field is initially zero, and T2 waits until it becomes non-zero then immediately deletes the h object. T1 calls h->signal() which sets the flag to non-zero. So the object will be deleted (by T2) while T1 is still potentially "inside" the signal() call. However, there are no further accesses to any fields of the handoff object in signal().
Is it defined behavior? I think it is clear that calling a member function (even an empty one) is UB after an object has been deleted, but how about returning from one?
TSAN thinks this is OK and I agree in practice this will work fine on sane architectures.