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

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

我 运行 进入了一个奇怪的 C++ 代码行为,不确定它是一个编译器错误还是仅仅是我的代码的 undefined/unspecified 行为。这是代码:

#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;
}

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

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

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

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

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

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

它看起来像一个竞争条件(在线程启动之前 vtable 没有完全构建)。这段代码格式错误吗?编译器错误?或者它应该是这样的?

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

Child 对象在 main 结束时被销毁。 Child 析构函数被执行,并有效地调用了 Parent 析构函数,其中 Parent 基(没有这样的)和数据成员(线程对象)被销毁。随着析构函数被调用到基链 类 中,对象的 动态类型 发生变化,其顺序与构造期间的变化顺序相反,因此此时的类型对象是 Parent.

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

这是常见的设计问题;你试图做的是一个经典的反模式。

Parent不能同时是线程管理器,启动线程等待线程终止:

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; }

这是两个截然不同的概念,具有完全不兼容的规范。

(而且线程对象一般用处不大。)