简体   繁体   中英

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 ?

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

This is common design issue; what you tried to do is a classical anti-pattern.

Parent cannot be at the same time a thread manager, starting a thread and waiting for thread termination:

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

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

and also a thread object:

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

These are two distinct concepts, with strictly incompatible specifications.

(And thread objects are not useful in general.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM