C++ 11 std :: thread和虚函数绑定

use*_*983 9 c++ multithreading thread-safety race-condition

我遇到了一个奇怪的C++代码行为,不确定它是编译器错误还是我的代码的未定义/未指定的行为.这是代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

代码的输出如下,这并不奇怪:

Child::init()
Child::fini()
Run Code Online (Sandbox Code Playgroud)

但是,如果函数调用"sleep(1)"被注释掉,输出将是:

Parent::init()
Parent::~fini()
Run Code Online (Sandbox Code Playgroud)

在Ubuntu 15.04上测试,gcc-4.9.2和clang-3.6.0都表现出相同的行为.编译器选项:

g++/clang++ test.cpp -std=c++11 -pthread
Run Code Online (Sandbox Code Playgroud)

它看起来像一个竞争条件(vtable在线程开始之前没有完全构造).这段代码是不正确的吗?编译器错误?或者它应该是这样的?

Che*_*Alf 5

@KerrekSB 评论道:

线程使用子对象,但是子对象在加入线程之前被销毁(因为加入只在子对象开始销毁之后发生)。

Child对象在年底被破坏main。的Child析构函数被执行,并有效地调用Parent析构函数,其中Parent碱(无此)和数据成员(线程对象)被破坏。当析构函数在基类链上被调用时,对象的动态类型会发生变化,其顺序与构造过程中的变化顺序相反,因此此时对象的类型是Parent

线程函数中的虚拟调用可以发生在Child析构函数调用之前、重叠或之后,并且在重叠的情况下,有一个线程访问正在被另一个线程更改的存储(实际上是 vtable 指针)。所以这是未定义的行为。