Can anyone help explain this unexpected behavior?
The Premise
I've created class Thread that contains a member std::thread
variable. Thread's ctor constructs the member std::thread
providing a pointer to a static function that calls a pure virtual function (to be implemented by base classes).
The Code
#include <iostream>
#include <thread>
#include <chrono>
namespace
{
class Thread
{
public:
Thread()
: mThread(ThreadStart, this)
{
std::cout << __PRETTY_FUNCTION__ << std::endl; // This line commented later in the question.
}
virtual ~Thread() { }
static void ThreadStart(void* pObj)
{
((Thread*)pObj)->Run();
}
void join()
{
mThread.join();
}
virtual void Run() = 0;
protected:
std::thread mThread;
};
class Verbose
{
public:
Verbose(int i) { std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl; }
~Verbose() { }
};
class A : public Thread
{
public:
A(int i)
: Thread()
, mV(i)
{ }
virtual ~A() { }
virtual void Run()
{
for (unsigned i = 0; i < 5; ++i)
{
std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
protected:
Verbose mV;
};
}
int main(int argc, char* argv[])
{
A a(42);
a.join();
return 0;
}
The Problem
As you may have already noticed, there's a subtle bug here: Thread::ThreadStart(...)
is called from the Thread
ctor context, therefore calling a pure/virtual function will not call the derived class' implementation. This is borne out by the runtime error:
pure virtual method called
terminate called without an active exception
Aborted
However, there is unexpected runtime behavior if I remove the call to std::cout
in the Thread
ctor:
virtual void {anonymous}::A::Run(){anonymous}::Verbose::Verbose(int): : 042
virtual void {anonymous}::A::Run(): 1
virtual void {anonymous}::A::Run(): 2
virtual void {anonymous}::A::Run(): 3
virtual void {anonymous}::A::Run(): 4
I.e. removing the call to std::cout
in the Thread
ctor seems to have the effect of being able to call a derived class' pure/virtual function from the base class` constructor context! This doesn't align with prior learning and experience.
Build environment in Cygwin x64 on Windows 10. gcc version is:
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I'm baffled by this observation and am burning with curiosity about what's going on. Can anyone shed light?
The behavior of this program is undefined, due to race condition.
But, if you want to reason about it, let's try.
For A
's construction, here's what happens:
mThread
is initialized. The OS schedules it to start at some point in the future.std::cout << __PRETTY_FUNCTION__ << std::endl;
- this is a fairly slow operation from the program's perspective.
A
constructor runs - initializing its vtable (this is not mandated by the stanard, but as far as I know, all implementations do this).
If this happens before mThread
is scheduled to start, you get the behaviour you observed. Otherwise, you get the pure virtual call.
Because those operations aren't in any way sequenced, the behaviour is undefined.
You can notice that you removed a fairly slow operation from your base's constructor, thus initializing your derived - and its vtable - much faster. Say, before the OS actually scheduled mThread
's thread to start. That being said, this did not fix the problem, just made encountering it less likely.
If you modify your example a bit, you'll notice that removing the IO code made the race harder to find, but fixed nothing.
virtual void Run()
{
for (unsigned i = 0; i < 1; ++i)
{
std::cout << __PRETTY_FUNCTION__ << ": " << i << std::endl;
// std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
main:
for(int i = 0; i < 10000; ++i){
A a(42);
a.join();
}
demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With