Please find the code snippet below:
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;
}
The output I am getting is:
Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4
I am confused how the destructors with address 0x7ffe27d1b06c and 0x2029c28 called and no constructors were called? Whereas the first and last constructor and destructor respectively are of the object I created.
Yes, the destructor is nothing more than a function. You can call it at any time. However, calling it without a matching constructor is a bad idea.
Constructors are also called when local or temporary class objects are created, and destructors are called when local or temporary objects go out of scope. You can call member functions from constructors or destructors.
Like constructors, can there be more than one destructors in a class? Question 3 Explanation: There can be only one destructor in a class.
Constructor helps to initialize the object of a class. Whereas destructor is used to destroy the instances.
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 : 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
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 : 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
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 : 0x104057020
Thread is joining...
Thread running at : 11
x : 11
Destroyed : 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 : 0x104055040
Move constructed : 0x104055160 (source=0x104055040)
Move constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Destroyed : 0x104055040
Thread is joining...
Thread running at : 11
Destroyed : 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With