Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 std::thread and virtual function binding

I ran into a weird C++ code behaviour, not sure whether it's a compiler bug or simply undefined/unspecified behaviour of my code. Here is the code:

#include <unistd.h>
#include <iostream>
#include <thread>

struct Parent {
    std::thread t;

    static void entry(Parent* p) {
        p->init();
        p->fini();
    }

    virtual ~Parent() { t.join(); }

    void start() { t = std::thread{entry, this}; }

    virtual void init() { std::cout << "Parent::init()" << std::endl; }
    virtual void fini() { std::cout << "Parent::fini()" << std::endl; }
};

struct Child : public Parent {
    virtual void init() override { std::cout << "Child::init()" << std::endl; }
    virtual void fini() override { std::cout << "Child::fini()" << std::endl; }
};

int main() {
    Child c;

    c.start();
    sleep(1); // <========== here is it

    return 0;
}

The output of the code would be the following, which isn't surprising:

Child::init()
Child::fini()

However, if the function call "sleep(1)" is commented out, the output would be:

Parent::init()
Parent::~fini()

Tested on Ubuntu 15.04, both gcc-4.9.2 and clang-3.6.0 show the same behaviour. Compiler options:

g++/clang++ test.cpp -std=c++11 -pthread

It looks like a race condition (the vtable not fully constructed before the thread starts). Is this code ill-formed ? a compiler bug ? or it's supposed to be like this ?

like image 746
user416983 Avatar asked Oct 16 '15 13:10

user416983


1 Answers

@KerrekSB commented:

The thread uses the child object, but the child object is destroyed before the thread is joined (because the joining only happens after the destruction of the child has begun).

The Child object is destroyed at the end of main. The Child destructor is executed, and effectively calls the Parent destructor, where Parent bases (no such) and data members (the thread object) are destroyed. As destructors are invoked up the chain of base classes the dynamic type of the object changes, in reverse order of how it changes during construction, so at this point the type of the object is Parent.

The virtual calls in the thread function can happen before, overlapping with or after the call of the Child destructor, and in the case of overlapping there's one thread accessing storage (in practice, the vtable pointer) that is being changed by another thread. So this is Undefined Behavior.

like image 183
Cheers and hth. - Alf Avatar answered Oct 14 '22 16:10

Cheers and hth. - Alf