【问题标题】:Sleep affecting which virtual member function is called by std::thread?睡眠影响std :: thread调用哪个虚拟成员函数?
【发布时间】:2018-04-20 09:47:14
【问题描述】:

我不确定这是否是 c++11 中的预期行为。这是我发现的一个示例。

#include <iostream>
#include <thread>
using namespace std;

class A {
public:
    virtual void a() = 0;
    thread t;
    A() : t(&A::a, this) {}
    virtual ~A() {
        t.join();
    }
};

class B : public A {
public:
    virtual void a() {
        cout << "B::a" << endl;
    }
};

int main() {
    B b;
    this_thread::sleep_for(chrono::seconds(1));
}

编译运行时

$ g++ -std=c++11 -pthread test.cpp -o test
$ ./test
B::a
$

但是当睡眠被移除时...

int main() {
    B b;
    //this_thread::sleep_for(chrono::seconds(1));
}

奇怪的事情发生了

$ g++ -std=c++11 -pthread test.cpp -o test
$ ./test
pure virtual method called
terminate called without an active exception
Aborted (core dumped)
$

这可能是一个错误吗?

【问题讨论】:

    标签: c++ multithreading c++11


    【解决方案1】:

    virtual 函数在构造函数和析构函数中的行为不同。在As 构造函数B 还没有初始化,这就是为什么Bs 虚函数还不能被调用。见virtual

    当从构造函数或析构函数直接或间接调用虚函数时(包括在类的非静态数据成员的构造或销毁期间,例如在成员初始化器列表中),以及调用的对象apply 是正在构造或销毁的对象,调用的函数是构造函数或析构函数类中的最终覆盖者,而不是在派生更多的类中覆盖它。换句话说,在构造或销毁期间,派生更多的类不存在。

    因此,该成员函数指针&amp;A::a 在调用它时在另一个线程中得到解析。此调用解析使用虚拟表,因为&amp;A::a 是指向虚拟成员函数的指针。构造函数所做的第一件事是将虚拟表指针设置为类的虚拟表。如果在调用(this-&gt;&amp;A::a)() 时已经输入了Bs 构造函数,那么它将调用B::a。在调用(this-&gt;&amp;A::a)() 的新线程与执行AB 构造函数的当前线程之间存在竞争条件。

    【讨论】:

    • 这并不能解释为什么在睡眠的版本中,B::a 会被调用。
    • 加上这一点,你的答案对我来说是正确的:两个版本都可以调用A::aB::a
    • 正式未定义的行为“任何此类数据竞争都会导致未定义的行为。” [intro.races]
    • @MaximEgorushkin 观察到this_thread::sleep_for 的效果应该纯属偶然吧? b 在调用之前构造。
    【解决方案2】:

    【讨论】:

    • 这是一个好观点,也是一篇好文章。虽然我也认为在构造函数/析构函数中调用虚函数有点代码味道,应该避免。如果没有别的,如果代码被编辑,那就是一个陷阱。
    猜你喜欢
    • 2015-11-20
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 2010-09-12
    相关资源
    最近更新 更多