Fonksiyon nesnesini kullanarak C ++ iş parçacığı, nasıl birden yıkıcı denir ama yapıcılar değil?


15

Lütfen kod snippet'ini aşağıda bulabilirsiniz:

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

Aldığım çıktı:

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

0x7ffe27d1b06c ve 0x2029c28 adresli yıkıcıların nasıl çağrıldığına ve hiçbir kurucu çağrılmadığına şaşkınım? Oysa ilk ve son kurucu ve yıkıcı oluşturduğum nesneye ait.


11
Fotokopi makinenizi ve taşıma cihazınızı da tanımlayın ve kullanın.
WhozCraig

İyice anlaşıldı. Kopya oluşturucunun çağrıldığı nesneyi ilettiğim için, doğru muyum? Ancak, hareket yapıcısı ne zaman çağrılır?
SHAHBAZ

Yanıtlar:


18

Kopya yapımı ve taşıma yapımında eksik araç var. Programınızda yapılacak basit bir değişiklik, inşaatların yapıldığı yerdeki kanıtları sağlayacaktır.

Kopya Oluşturucu

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

Çıktı (adresler değişir)

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

Kopya Oluşturucu ve Taşı Oluşturucu

Bir taşıma kasası sağlarsanız, aksi takdirde bu kopyalardan en az biri için tercih edilir:

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

Çıktı (adresler değişir)

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

Referans Sarılmış

Bu kopyalardan kaçınmak isterseniz, çağrılabilirinizi bir referans sarmalayıcıya ( std::ref) sarabilirsiniz . tİplik geçirme kısmı yapıldıktan sonra kullanmak istediğiniz için durumunuz için bu geçerlidir. Pratikte , çağrı nesnelerine yapılan referanslara karşı iplik geçirirken çok dikkatli olmalısınız, çünkü nesnenin ömrü en az referansı kullanan iş parçacığı kadar uzamalıdır.

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

Çıktı (adresler değişir)

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

Ben kopya-ctor ve move-ctor aşırı yükleri tutmak rağmen, ne de, çağrı sarıcı şimdi kopya / taşınan şey olduğu için çağrıldı; referans verdiği şey değil. Ayrıca, bu son yaklaşım muhtemelen aradığınızı sağlar; t.xgeri main, aslında, değiştirilmiş 11. Önceki denemelerde değildi. Ancak bunu yeterince vurgulayamıyorum: bunu yaparken dikkatli olun . Nesne ömrü kritiktir .


Hareket Et, Ve Hiçbir Şey Ama

Son olarak, törneğinizde olduğu gibi tutmaya ilgi duymuyorsanız , örneği yol boyunca hareket ederek doğrudan iş parçacığına göndermek için taşıma semantiğini kullanabilirsiniz.

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

Çıktı (adresler değişir)

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

Burada nesnenin yaratıldığını, söz konusu aynı değere ilişkin rvalue referansının daha sonra doğrudan gönderildiği std::thread::thread()ve o noktadan itibaren iş parçacığının sahip olduğu nihai dinlenme yerine tekrar taşındığı görebilirsiniz. Hiçbir kopyalayıcı dahil değildir. Gerçek dtorlar iki mermiye ve nihai hedef beton nesnesine karşıdır.


5

Yorumlarda yayınlanan ek sorunuz için:

Taşıma yapıcısı ne zaman çağrılır?

İlk kurucu std::threadilk argümanının bir kopyasını oluşturur (by decay_copy) - yani kopya kurucu çağrılır. (Bir durumunda bu Not rvalue gibi değişken, thread t1{std::move(t)};ya da thread t1{tFunc{}};, hareket yapıcı yerine olarak isimlendirilirler.)

Sonucu decay_copybir olduğunu , geçici yığın kahntılannin söyledi. Ancak, çağıran bir iş parçacığıdecay_copy tarafından gerçekleştirildiğinden , bu geçici yığınında bulunur ve yapıcı sonunda yok edilir . Sonuç olarak, geçidin kendisi yeni oluşturulan bir evre tarafından doğrudan kullanılamaz.std::thread::thread

Functor'ı yeni iş parçacığına "iletmek" için, başka bir yerde yeni bir nesnenin oluşturulması gerekir ve bu, move yapıcısının çağrıldığı yerdir . (Eğer mevcut değilse, kopya oluşturucu çağrılır.)


Ertelenmiş geçici materyalizasyonun neden burada uygulanmadığını merak edebileceğimizi unutmayın . Örneğin, bu canlı demoda iki yerine yalnızca bir kurucu çağrılır. C ++ Standart kitaplığının uygulanmasına ilişkin bazı dahili uygulama ayrıntılarının, std::threadyapıcı için uygulanacak optimizasyonu engellediğine inanıyorum .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.