首页 > 解决方案 > 为什么在 C++ 中的类中拥有一个线程并使用构造函数调用它如此困难?

问题描述

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

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

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

标签: c++c++11pthreadsstdthread

解决方案


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

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

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

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

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

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

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

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

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

如果您正在编写这样的代码,并且对成员的声明顺序有重要的依赖关系,那么您应该感谢未来的维护者,包括您自己,用注释记录这种依赖关系:

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

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

特别是,如果您的计划是a在派生类中添加虚函数并覆盖它们,那么您注定要失败。线程启动时,基类构造函数仍在运行,派生类构造函数的成员初始化器和主体尚未运行。它们将在基类构造函数之后运行,并且您再次陷入未定义的行为。


推荐阅读