C++ thread using function object, how are multiple destructors are called but not the constructors? C++ thread using function object, how are multiple destructors are called but not the constructors? multithreading multithreading

C++ thread using function object, how are multiple destructors are called but not the constructors?


You are missing instrumenting copy-construction and move construction. A simple modification to your program will provide evidence that is where the constructions are taking place.

Copy Constructor

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

Output (addresses vary)

Constructed : 0x104055020Copy constructed : 0x104055160 (source=0x104055020)Copy constructed : 0x602000008a38 (source=0x104055160)Destroyed : 0x104055160Thread running at : 11Destroyed : 0x602000008a38Thread is joining...x : 1Destroyed : 0x104055020

Copy Constructor and Move Constructor

If you provide a move ctor it will be preferred for at least one of those otherwise-copies:

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

Output (addresses vary)

Constructed : 0x104057020Copy constructed : 0x104057160 (source=0x104057020)Move constructed : 0x602000008a38 (source=0x104057160)Destroyed : 0x104057160Thread running at : 11Destroyed : 0x602000008a38Thread is joining...x : 1Destroyed : 0x104057020

Reference Wrapped

If you want to avoid those copies you can wrap your callable in a reference wrapper (std::ref). Since you want to utilize t after the threading part is done, this is viable for your situation. In practice you must be very careful when threading against references to call objects, as lifetime of the object must extend at least as long as the thread utilizing the reference.

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

Output (addresses vary)

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

Note even though I kept the copy-ctor and move-ctor overloads, neither were called, as the reference wrapper is now the thing being copy/moved; not the thing it references. Also, this final approach delivers what you were probably looking for; t.x back in main is, in fact, modified to 11. It was not in the prior attempts. Can't stress this enough, however: be careful doing this. Object lifetime is critical.


Move, And Nothing But

Finally, if you have no interest in retaining t as you have in your example, you can use move semantics to send the instance straight to the thread, moving along the way.

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

Output (addresses vary)

Constructed : 0x104055040Move constructed : 0x104055160 (source=0x104055040)Move constructed : 0x602000008a38 (source=0x104055160)Destroyed : 0x104055160Destroyed : 0x104055040Thread is joining...Thread running at : 11Destroyed : 0x602000008a38

Here you can see the object is created, the rvalue reference to said-same then sent straight to std::thread::thread(), where it is moved again to its final resting place, owned by the thread from that point forward. No copy-ctors are involved. The actual dtors are against two shells and the final-destination concrete object.


As for your additional question posted in comments:

When is the move constructor called?

The constructor of std::thread first creates a copy of its first argument (by decay_copy) — that is where copy constructor is called. (Note that in case of an rvalue argument, such as thread t1{std::move(t)}; or thread t1{tFunc{}};, move constructor would be called instead.)

The result of decay_copy is a temporary that resides on the stack. However, since decay_copy is performed by a calling thread, this temporary resides on its stack and is destroyed at the end of std::thread::thread constructor. Consequently, the temporary itself cannot be used by a new created thread directly.

To "pass" the functor to the new thread, a new object needs to be created somewhere else, and this is where move constructor is invoked. (If it did not exist, copy constructor would be invoked instead.)


Note that we might wonder why deferred temporary materialization is not applied here. For instance, in this live demo, only one constructor is invoked instead of two. I believe that some internal implementation details of the implementation of the C++ Standard library hinder the optimization to be applied for std::thread constructor.