There are two problems here, both related to the absence of synchronization.
The first problem is that a virtual call could be made on an object that has not yet been fully constructed. A new thread is created before the construction of B
, f()
is called inside that thread and there is no guarantee that by that time B
is fully constructed. The behaviour is undefined.
Going into some implementation details, virtual calls are typically implemented using virtual tables. A pointer to a virtual table is set during B
's construction by a compiler, and if you call f()
before that pointer is correctly set, you're likely to call A::f()
instead of B::f()
. Calling A::f()
generates the "pure virtual method called" error message. Note that f()
is called inside a thread, not inside a constructor, so that the call will be virtual and won't be resolved to A::f()
at compile time.
The second problem is that a virtual call could be made on an object that has already been destructed. The destructor of A
, in which you join a thread, is called after the destruction of B
. Again, making a virtual call on a destructed object is undefined behaviour.
To get some insight into what's going on, you can add two mutexes:
std::mutex m1, m2;
struct A {
std::thread thread;
A() : thread(&A::thread_f, this) {
std::cout << "A()" << std::endl;
}
~A() {
std::cout << "~A()" << std::endl;
thread.join();
}
virtual void f() = 0;
void thread_f() {
std::cout << "thread_f()" << std::endl;
m1.lock();
f();
m2.unlock();
}
};
struct B : A {
B() {
std::cout << "B()" << std::endl;
m1.unlock();
}
~B() {
m2.lock();
std::cout << "~B()" << std::endl;
}
void f() override {
std::cout << "B::f()" << std::endl;
}
};
int main() {
m1.lock();
m2.lock();
B b;
}
Now you'll see the expected "B::f()
" output. Demo. I would hardly call this a solution, though.
The answer by kenash0625 addresses the first problem, but it doesn't address the second one.