为什么在 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 的顺序构造的; t
在 data
之前声明,因此 t
在 data
初始化之前构造。无论它们在构造函数的初始化列表中的顺序如何。
因此,解决方案是更改声明的顺序:
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 构造函数之后,再次,你陷入了未定义的行为。
我一直在尝试从 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 的顺序构造的; t
在 data
之前声明,因此 t
在 data
初始化之前构造。无论它们在构造函数的初始化列表中的顺序如何。
因此,解决方案是更改声明的顺序:
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 构造函数之后,再次,你陷入了未定义的行为。