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

SHA*_*BAZ 12 c++ multithreading destructor

请在下面找到代码片段:

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;
}
Run Code Online (Sandbox Code Playgroud)

我得到的输出是:

Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4
Run Code Online (Sandbox Code Playgroud)

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

Who*_*aig 16

您缺少检测复制构造和移动构造。对您的程序进行简单的修改,即可提供正在构建的证据。

复制构造函数

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出(地址不同)

#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;
}
Run Code Online (Sandbox Code Playgroud)

复制构造函数和移动构造函数

如果您提供搬家公司,则至少应优先选择以下一种副本:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出(地址不同)

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
Run Code Online (Sandbox Code Playgroud)

参考包装

如果要避免这些副本,可以将可调用对象包装在参考包装中(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;
}
Run Code Online (Sandbox Code Playgroud)

输出(地址不同)

#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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,尽管我保留了copy-ctor和move-ctor重载,但都没有被调用,因为引用包装器现在是要被复制/移动的东西。不是它引用的东西。同样,这种最终方法可以满足您的需求。实际上,将t.xback in main修改为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;
}
Run Code Online (Sandbox Code Playgroud)

输出(地址不同)

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
Run Code Online (Sandbox Code Playgroud)

在这里,您可以看到对象已创建,对said-same的右值引用随后直接发送到std::thread::thread(),在该对象处再次移动到其最终的静止位置,该位置从该线程开始一直归线程所有。不涉及复制指针。实际的干向是抵靠两个壳和最终目标混凝土对象。


Dan*_*ica 5

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

什么时候调用移动构造函数?

std::threadfirst的构造函数创建了它的第一个参数 (by decay_copy) 的副本——这是调用复制构造函数的地方。(请注意,在右值参数的情况下,例如thread t1{std::move(t)};or thread t1{tFunc{}};改为调用移动构造函数。)

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

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


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