6

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?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • Nothing unexpected here. When the object is only constructed as far as any specific base class A, the only virtual function implementations available are those visible from A. – user207421 Oct 29 '16 at 09:15
  • It's a shame we do not have a post-ctor in addition. Would be so useful here... – Deduplicator Oct 29 '16 at 09:25

1 Answers1

9

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:

  1. mThread is initialized. The OS schedules it to start at some point in the future.
  2. std::cout << __PRETTY_FUNCTION__ << std::endl; - this is a fairly slow operation from the program's perspective.

  3. 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

krzaq
  • 16,240
  • 4
  • 46
  • 61
  • 1
    Good explanation! – πάντα ῥεῖ Oct 29 '16 at 09:15
  • If I understand you correctly: if `Thread` has no `cout`, it initializes quickly, therefore its vtable _may_ be initialized before the OS schedules the `std::thread`, and if so, by the time the `std::thread` invokes `ThreadStart`, the vtable entry for `Run()` will be populated, thereby invoking `A::Run()`. Did I get you correctly? – StoneThrow Oct 29 '16 at 09:18
  • @StoneThrow yeah. – krzaq Oct 29 '16 at 09:20
  • @krzaq: Thank you; that was a great explanation. So I was actually mistaken when I said `ThreadStart()` is called from the `Thread` ctor context. It's actually called in a separate context: that of the spawned `std::thread`. – StoneThrow Oct 29 '16 at 09:22