関数オブジェクトを使用するC ++スレッド、複数のデストラクタはどのように呼び出されますが、コンストラクタは呼び出されないのですか?


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のデストラクタがどのように呼び出され、コンストラクタが呼び出されなかったか混乱していますか?一方、最初と最後のコンストラクタとデストラクタは、それぞれ私が作成したオブジェクトのものです。


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

コンストラクターのコピーとコンストラクターの移動

ムーブトラクターを提供する場合、それ以外のコピーでは少なくとも1つ優先されます。

#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のオーバーロードは保持しましたが、どちらも呼び出されなかったことに注意してください。それが参照するものではありません。また、この最終的なアプローチは、おそらく探していたものを提供します。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

ここでは、オブジェクトが作成され、sayed-sameへの右辺値参照がに直接送信std::thread::thread()されて、スレッドが所有する最後の休憩場所に再び移動し、それ以降そのスレッドが所有していることがわかります。コピー俳優は関与していません。実際のdtorは、2つのシェルと最終的な宛先のコンクリートオブジェクトに対してです。


5

コメントに投稿された追加の質問については:

移動コンストラクタはいつ呼び出されますか?

std::threadfirst のコンストラクターは、最初の引数のコピーを(によってdecay_copy)作成します—これは、コピーコンストラクターが呼び出される場所です。(の場合と注意右辺値などの引数、thread t1{std::move(t)};またはthread t1{tFunc{}};移動コンストラクタが代わりに呼び出されることになります。)

の結果は、スタック上にdecay_copyある一時的なものです。ただし、これdecay_copy呼び出しスレッドによって実行されるため、一時的にそのスタックに常駐し、std::thread::threadコンストラクターの最後で破棄されます。したがって、一時自体は、新しく作成されたスレッドで直接使用することはできません。

ファンクタを新しいスレッドに「渡す」には、新しいオブジェクトを別の場所に作成する必要があり、ここで移動コンストラクタが呼び出されます。(存在しない場合は、代わりにコピーコンストラクターが呼び出されます。)


ここで、なぜ一時的な実体化が適用さないのか不思議に思うかもしれません。たとえば、このライブデモでは、2つではなく1つのコンストラクタのみが呼び出されます。C ++標準ライブラリの実装の内部実装の詳細によっては、std::threadコンストラクタに適用される最適化が妨げられると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.