简体   繁体   English

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

[英]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. 我遇到了一个奇怪的C ++代码行为,不确定它是编译器错误还是我的代码的未定义/未指定的行为。 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: 但是,如果函数调用“sleep(1)”被注释掉,输出将是:

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

Tested on Ubuntu 15.04, both gcc-4.9.2 and clang-3.6.0 show the same behaviour. 在Ubuntu 15.04上测试,gcc-4.9.2和clang-3.6.0都表现出相同的行为。 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). 它看起来像一个竞争条件(vtable在线程开始之前没有完全构造)。 Is this code ill-formed ? 这段代码是不正确的吗? a compiler bug ? 编译器错误? or it's supposed to be like this ? 或者它应该是这样的?

@KerrekSB commented: @KerrekSB评论道:

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 . Child对象在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. Child析构函数被执行,并有效地调用Parent析构函数,其中Parent基(没有这样)和数据成员(线程对象)被销毁。 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 . 当析构函数被调用到基类链时,对象的动态类型会在构造期间以相反的顺序改变,因此此时对象的类型是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. 线程函数中的虚拟调用可以在Child析构函数调用之前,重叠或之后发生,并且在重叠的情况下,有一个线程访问由另一个线程更改的存储(实际上是vtable指针)。 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: Parent不能同时是一个线程管理器,启动一个线程并等待线程终止:

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.) (并且线程对象通常没用。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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