C++使用函数对象的线程,如何调用多个析构函数而不是构造函数?



请在下面找到代码片段:

class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX(){ return x; }
};
int main()
{
tFunc t;
thread t1(t);
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}

我得到的输出是:

Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4

我很困惑如何调用地址0x7ffe27d1b06c和0x2029c28的析构函数而没有调用构造函数?而第一个和最后一个构造函数和析构函数分别是我创建的对象。

您缺少检测复制构造和移动构造。对程序的简单修改将提供证据,证明这是进行构造的地方。

复制构造函数

#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
tFunc t;
thread t1{t};
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}

输出(地址不同(

Constructed : 0x104055020
Copy constructed : 0x104055160 (source=0x104055020)
Copy constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104055020
复制构造函数

和移动构造函数

如果您提供移动 ctor,则至少其中一个副本将首选:

#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
tFunc t;
thread t1{t};
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}

输出(地址不同(

Constructed : 0x104057020
Copy constructed : 0x104057160 (source=0x104057020)
Move constructed : 0x602000008a38 (source=0x104057160)
Destroyed : 0x104057160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104057020

引用换行

如果你想避免这些副本,你可以将你的可调用包装包装在引用包装器(std::ref(。由于您希望在螺纹部分完成后使用t,因此这适用于您的情况。实际上,在线程化对调用对象的引用时,您必须非常小心,因为对象的生存期必须至少与使用该引用的线程一样长。

#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
tFunc t;
thread t1{std::ref(t)}; // LOOK HERE
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}

输出(地址不同(

Constructed : 0x104057020
Thread is joining...
Thread running at : 11
x : 11
Destroyed : 0x104057020

请注意,即使我保留了 copy-ctor 和 move-ctor 重载,也没有调用它们,因为引用包装器现在是被复制/移动的东西;而不是它引用的东西。此外,最后一种方法提供了您可能正在寻找的内容; 事实上,maint.x被修改为11.这在之前的尝试中是没有的。但是,这一点怎么强调都不为过:小心这样做。对象生存期至关重要


移动,仅此而已

最后,如果您没有兴趣像示例中那样保留t,则可以使用 move 语义将实例直接发送到线程,并沿途移动。

#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
thread t1{tFunc()}; // LOOK HERE
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
return 0;
}

输出(地址不同(

Constructed : 0x104055040
Move constructed : 0x104055160 (source=0x104055040)
Move constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Destroyed : 0x104055040
Thread is joining...
Thread running at : 11
Destroyed : 0x602000008a38

在这里你可以看到对象被创建,对 said-same 的右值引用然后直接发送到std::thread::thread(),在那里它再次移动到它的最终休息位置,从那一点开始由线程拥有。不涉及复制者。实际的dtors针对两个壳体和最终目的地的混凝土物体。

至于您在评论中发布的其他问题:

何时调用移动构造函数?

std::thread的构造函数首先创建其第一个参数的副本(通过decay_copy(——这就是调用复制构造函数的地方。(请注意,如果是右值参数,例如thread t1{std::move(t)};thread t1{tFunc{}};,将改为调用move 构造函数

decay_copy的结果是驻留在堆栈上的临时结果。但是,由于decay_copy调用线程执行,因此此临时驻留在其堆栈上,并在构造函数结束时销毁std::thread::thread。因此,临时本身不能由新创建的线程直接使用。

要将函子"传递"到新线程,需要在其他地方创建一个新对象,这就是调用move 构造函数的地方。(如果它不存在,则将调用复制构造函数。


请注意,我们可能想知道为什么这里不应用延迟临时实现。例如,在此实时演示中,仅调用一个构造函数而不是两个构造函数。我相信C++标准库实现的一些内部实现细节阻碍了应用于std::thread构造函数的优化。

最新更新