为什么在 c++ 中的 class 中有一个线程并用构造函数调用它如此困难?

Why is it so difficult to have a thread inside a class in c++ and invoke it with a constructor?

我一直在尝试从 class 构造函数调用线程,但无济于事。 为什么将函数或对象传递给线程如此困难。

#include <iostream>
#include <thread>
class a{
        public:
        a();
        std::thread t;
        int data;
        //virtual void fn();
};

void fn(a *p)
{
        std::cout << "Thread Function : " << p->data << std::endl;
}

a::a():data(10)
//a::a():data(10),t(fn,this)
{

        void (*fnp)(a *p) = fn;
        fn(this);
        fnp(this);
        t(fnp, this);
}

int main ()
{
        a av;
        return 0;
}

其输出如下:

preetam@preetam-GL702ZC:~/Desktop$ g++ v.cpp -lpthread
v.cpp: In constructor ‘a::a()’:
v.cpp:23:13: error: no match for call to ‘(std::thread) (void (*&)(a*), a*)’
  t(fnp, this);

我只想从构造函数启动一个线程并让该线程轻松访问 class 成员。

有几种方法可以做到这一点。与问题中的代码最相似的是:

a::a() : data(10) {
    std::swap(t, std::thread(fn, this));
}

那是因为线程是在线程对象的构造函数中创建的。在此代码中,t 被默认构造,因为它未在初始化列表中提及。构造函数主体中的代码构造一个临时线程对象并将其与默认构造的 t 对象交换。交换后,t 拥有 运行ning fn(this) 的线程,临时对象拥有默认构造的线程。在语句结束时,临时线程对象被销毁。

除了创建和交换方法,您还可以直接在初始化列表中创建线程:

a::a() : data(10), t(fn, this) { // won't work, though...
}

这里的问题有点微妙:尽管将 data 的初始化器放在 t 的初始化器前面,但 t 的构造器在 运行 之前data 被初始化。所以有一场数据竞赛。发生这种情况是因为成员对象是按照 declaration 的顺序构造的; tdata 之前声明,因此 tdata 初始化之前构造。无论它们在构造函数的初始化列表中的顺序如何。

因此,解决方案是更改声明的顺序:

class a {
    int data;
    std::thread t;
    a();        
};

现在之前的代码可以正常工作了。

如果您正在编写这样的代码,并且对成员声明的顺序具有重要的依赖性,那么您欠未来的维护者(包括您自己)以注释来记录该依赖性:

class a {
    // IMPORTANT: data must be declared before t
    int data;
    std::thread t;
    a();        
};

另请注意,对于这两种方法,启动的线程正在对其构造函数尚未完成 运行ning 的对象进行操作。如果线程函数使用该对象的任何成员,并且构造函数在启动线程后修改了这些成员,那么您就有了数据竞争,因此,未定义的行为。

特别是,如果您的计划是将虚函数添加到 a 并在派生的 class 中覆盖它们,那么您注定要失败。当线程启动时,基础class构造函数仍然是运行ning,而派生class构造函数的成员初始化器和主体还没有运行。他们将 运行 基础 class 构造函数之后,再次,你陷入了未定义的行为。