The code shown shows race status and undefined behavior.
In your main ():
C c;
Immediately after returning set_ready() , the execution thread leaves main() . This leads to the immediate destruction of c , starting with the superclass c and continuing to destroy B , then A
c declared automatically. This means that as soon as main() returns, it has disappeared. Joined the invisible choir. This is no longer the case. He ceased to exist. This is an ex object.
Your join() is in the superclass destructor . Nothing stops c from destruction. The destructor will only pause and wait to join the thread when the superclass is destroyed, but c starts to be destroyed immediately!
Once the superclass of class c is destroyed, its virtual method will no longer exist, and the call to the virtual function will complete the virtual function in the base class.
Meanwhile, another executable thread is expecting a mutex and a condition variable. The condition of the race is that you have no guarantee that another execution thread will wake up and begin execution before the parent thread destroys c , which it does immediately after signaling the condition variable.
Everything that signals a condition variable gives you the fact that any thread of execution rotates on a condition variable, this thread of execution will start execution. Eventually. This thread could, on a very busy server, start executing seconds later, after it has been signaled through a condition variable. His object is long gone. It was in automatic mode, and main() destroyed it (or rather, the subclass c has already been destroyed, and A destructor is waiting to join the stream).
The behavior you observe is the parent thread controlling the destruction of the superclass c before std::thread around the invocation of the virtual method after receiving the signal from the condition variable and unlocking its mutex.
This is a race condition.
In addition, making a virtual method call while destroying the virtual object is no longer a starter. This behavior is undefined. Even if a thread of execution ends in an overridden method, its object is simultaneously destroyed by another thread. At this moment you are very screwed up, no matter how you turn.
Lesson Learn: Setting> to do something in the this object is a minefield of undefined behavior. There are ways to do it right, but it's hard.