เธรด C ++ ที่ใช้ฟังก์ชั่นวัตถุจะมีตัวเรียกว่า destructors หลายตัว แต่ไม่ใช่ตัวสร้าง


15

โปรดค้นหาข้อมูลโค้ดด้านล่าง:

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 นั้นเรียกได้อย่างไรและไม่มีการเรียกตัวสร้าง ในขณะที่คอนสตรัคเตอร์แรกและสุดท้ายและ destructor ตามลำดับเป็นของวัตถุที่ฉันสร้าง


11
กำหนดและเครื่องดนตรีเครื่องถ่ายเอกสารและเครื่องย้ายของคุณเช่นกัน
WhozCraig

เข้าใจแล้ว ตั้งแต่ฉันผ่านวัตถุตัวสร้างสำเนาถูกเรียกฉันถูกต้องหรือไม่ แต่ตัวสร้างการย้ายจะถูกเรียกเมื่อใด
SHAHBAZ

คำตอบ:


18

คุณกำลังขาดการคัดลอกเครื่องมือก่อสร้างและย้ายสิ่งก่อสร้าง การดัดแปลงอย่างง่ายในโปรแกรมของคุณจะแสดงหลักฐานว่ามีการก่อสร้างเกิดขึ้นที่ใด

คัดลอกตัวสร้าง

#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

เอกสารอ้างอิง

หากคุณต้องการหลีกเลี่ยงสำเนาเหล่านั้นคุณสามารถห่อ callable ของคุณใน wrapper อ้างอิง ( 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 มากเกินไปก็ไม่ได้ถูกเรียกเนื่องจาก wrapper อ้างอิงตอนนี้เป็นสิ่งที่ถูกคัดลอก / ย้าย ไม่ใช่สิ่งที่อ้างอิง นอกจากนี้วิธีการขั้นสุดท้ายนี้ให้สิ่งที่คุณกำลังมองหาอยู่ t.xกลับมาอยู่ในมีที่ในความเป็นจริงการแก้ไขให้main 11มันไม่ได้อยู่ในความพยายามก่อนหน้า ไม่สามารถความเครียดพอนี้อย่างไร: ต้องระวังการทำเช่นนี้ อายุการใช้งานวัตถุเป็นสำคัญ


ย้ายและไม่มีอะไรนอกจาก

ในที่สุดหากคุณไม่สนใจที่จะเก็บรักษา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()
{
    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

ที่นี่คุณสามารถเห็นวัตถุที่ถูกสร้างขึ้นการอ้างอิง rvalue ไปที่กล่าวเดียวกันแล้วส่งตรงไปยังstd::thread::thread()ที่ที่มันถูกย้ายอีกครั้งไปยังสถานที่พำนักสุดท้ายเป็นเจ้าของกระทู้จากจุดนั้นไปข้างหน้า ไม่มีตัวคัดลอกที่เกี่ยวข้อง dtors ที่เกิดขึ้นจริงนั้นขัดแย้งกับสองกระสุนและวัตถุคอนกรีตปลายทางสุดท้าย


5

สำหรับคำถามเพิ่มเติมของคุณโพสต์ในความคิดเห็น:

ตัวสร้างการย้ายจะถูกเรียกเมื่อใด

ตัวสร้างของstd::threadแรกสร้างสำเนาของอาร์กิวเมนต์แรก (โดยdecay_copy) - นั่นคือที่ตัวสร้างสำเนาเรียกว่า (หมายเหตุว่าในกรณีของrvalueโต้แย้งเช่นthread t1{std::move(t)};หรือthread t1{tFunc{}};, ย้ายคอนสตรัคจะเรียกแทน.)

ผลมาจากการdecay_copyเป็นชั่วคราวที่อยู่ในกอง อย่างไรก็ตามเนื่องจากdecay_copyถูกดำเนินการโดยการเรียกเธรดชั่วคราวนี้จะอยู่ในสแต็กและถูกทำลายในตอนท้ายของตัวstd::thread::threadสร้าง ดังนั้นเธรดใหม่ที่สร้างขึ้นเองจึงไม่สามารถใช้งานได้ชั่วคราว

หากต้องการ "ส่ง" functor ไปยังเธรดใหม่วัตถุใหม่จะต้องถูกสร้างขึ้นที่อื่นและนี่คือจุดที่ตัวสร้างการย้ายถูกเรียกใช้ (ถ้ามันไม่มีอยู่ตัวสร้างการคัดลอกจะถูกเรียกใช้แทน)


โปรดทราบว่าเราอาจสงสัยว่าเหตุใดการทำให้เป็นรูปธรรมชั่วคราวที่เลื่อนออกไปจึงไม่ได้ถูกนำมาใช้ที่นี่ ตัวอย่างเช่นในการสาธิตสดนี้มีเพียงหนึ่งคอนสตรัคที่ถูกเรียกใช้แทนสอง ฉันเชื่อว่ารายละเอียดการติดตั้งภายในบางส่วนของการใช้งานไลบรารี C ++ Standard ขัดขวางการเพิ่มประสิทธิภาพที่จะใช้สำหรับตัวstd::threadสร้าง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.