Chủ đề C ++ sử dụng đối tượng hàm, làm thế nào nhiều hàm hủy được gọi nhưng không phải là các hàm tạo?


15

Vui lòng tìm đoạn mã dưới đây:

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;
}

Đầu ra tôi nhận được là:

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

Tôi bối rối làm thế nào các hàm hủy có địa chỉ 0x7ffe27d1b06c và 0x2029c28 được gọi và không có hàm tạo nào được gọi? Trong đó, hàm tạo và hàm hủy đầu tiên và cuối cùng tương ứng là của đối tượng tôi đã tạo.


11
Xác định và thiết bị sao chép-ctor và move-ctor của bạn là tốt.
WhozCraig

Vâng, hiểu rồi. Vì tôi đang truyền đối tượng mà hàm tạo sao chép được gọi, tôi có đúng không? Nhưng, khi nào thì constructor được gọi?
SHAHBAZ

Câu trả lời:


18

Bạn đang thiếu thiết bị sao chép xây dựng và di chuyển xây dựng. Một sửa đổi đơn giản cho chương trình của bạn sẽ cung cấp bằng chứng đó là nơi các công trình đang diễn ra.

Sao chép xây dựng

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

Đầu ra (địa chỉ khác nhau)

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

Sao chép Trình xây dựng và Di chuyển Trình xây dựng

Nếu bạn cung cấp một ctor di chuyển, nó sẽ được ưu tiên cho ít nhất một trong những bản sao khác:

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

Đầu ra (địa chỉ khác nhau)

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

Tham khảo gói

Nếu bạn muốn tránh những bản sao đó, bạn có thể gói có thể gọi được trong một trình bao bọc tham chiếu ( std::ref). Vì bạn muốn sử dụng tsau khi hoàn thành phần xâu chuỗi, điều này khả thi cho tình huống của bạn. Trong thực tế, bạn phải rất cẩn thận khi xâu chuỗi các tham chiếu để gọi các đối tượng, vì thời gian tồn tại của đối tượng phải kéo dài ít nhất là khi luồng sử dụng tham chiếu.

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

Đầu ra (địa chỉ khác nhau)

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

Lưu ý mặc dù tôi đã giữ quá tải copy-ctor và move-ctor, nhưng không được gọi, vì trình bao bọc tham chiếu bây giờ là thứ được sao chép / di chuyển; không phải là thứ nó tham khảo. Ngoài ra, phương pháp cuối cùng này cung cấp những gì bạn có thể đang tìm kiếm; t.xtrở lại trong mainlà, trên thực tế, sửa đổi để 11. Đó không phải là trong những nỗ lực trước đó. Tuy nhiên, không thể nhấn mạnh điều này đủ: hãy cẩn thận khi làm điều này . Đối tượng trọn đời là rất quan trọng .


Di chuyển, và không có gì nhưng

Cuối cùng, nếu bạn không có hứng thú với việc giữ lại tnhư trong ví dụ của mình, bạn có thể sử dụng ngữ nghĩa di chuyển để gửi cá thể thẳng đến luồng, di chuyển dọc đường.

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

Đầu ra (địa chỉ khác nhau)

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

Ở đây bạn có thể thấy đối tượng được tạo ra, tham chiếu giá trị cho cùng nói sau đó được gửi thẳng đến std::thread::thread(), nơi nó được di chuyển trở lại nơi an nghỉ cuối cùng của nó, thuộc sở hữu của luồng từ thời điểm đó trở đi. Không có bản sao có liên quan. Các dtor thực tế chống lại hai vỏ và vật thể bê tông đích cuối cùng.


5

Đối với câu hỏi bổ sung của bạn được đăng trong ý kiến:

Khi nào thì constructor được gọi?

Hàm tạo của std::threadlần đầu tiên tạo một bản sao của đối số đầu tiên (bởi decay_copy) - đó là nơi mà hàm tạo sao chép được gọi. (Lưu ý rằng trong trường hợp của một rvalue cãi nhau, chẳng hạn như thread t1{std::move(t)};hay thread t1{tFunc{}};, nhà xây dựng di chuyển sẽ được gọi thay thế.)

Kết quả decay_copytạm thời nằm trên ngăn xếp. Tuy nhiên, do decay_copyđược thực hiện bởi một luồng gọi , tạm thời này nằm trên ngăn xếp của nó và bị phá hủy ở cuốistd::thread::thread tạo. Do đó, bản thân tạm thời không thể được sử dụng bởi một luồng được tạo mới trực tiếp.

Để "chuyển" functor sang luồng mới, một đối tượng mới cần được tạo ở một nơi khác và đây là nơi mà hàm tạo di chuyển được gọi. (Nếu nó không tồn tại, thay vào đó, constructor sẽ được gọi.)


Lưu ý rằng chúng tôi có thể tự hỏi tại sao vật chất tạm thời hoãn lại không được áp dụng ở đây. Ví dụ, trong bản demo trực tiếp này , chỉ có một hàm tạo được gọi thay vì hai. Tôi tin rằng một số chi tiết triển khai nội bộ của việc triển khai thư viện C ++ Standard cản trở việc tối ưu hóa được áp dụng cho hàm std::threadtạo.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.