#include <iostream>
#include <thread>
template<int Num>
class MyClass {
public:
MyClass(int val) : val_(val) {}
// Copy constructor
MyClass(const MyClass& other) : val_(other.val_) {
std::cout << "Copy constructor called" << std::endl;
}
// Move constructor
MyClass(MyClass&& other) noexcept : val_(other.val_) {
std::cout << "Move constructor called" << std::endl;
}
private:
int val_;
};
template<int Num>
void threadFunction(MyClass<Num> myObj) {
std::cout << "Inside thread" << std::endl;
}
int main() {
MyClass<1> obj(42);
std::thread t1(threadFunction<1>, obj); // <-- cally copy AND move
std::thread t2(threadFunction<1>, std::ref(obj)); // <-- calls only copy
t1.join();
t2.join();
return 0;
}
I know that std::ref(obj)
is actually not necessary in this example based on the answer here:
c++ thread function accepting object by value: why does std::ref(obj) compile?
However, different constructors are invoked depending on how obj is passed: copy+move constructor for obj
and only copy constructor for std::ref(obj)
.
Why is that?
std::thread t1(threadFunction<1>, obj);
Here, the obj
is first copied into the std::thread
(on the current i.e. main thread) so that when threadFunction<1>
is eventually ready to be invoked by the new thread, threadFunction<1>
can be called with that copy.
Note that using this constructor has the effect of
std::invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...)
The copy is the result of auto(...)
, which is an rvalue. Then, the move constructor of MyClass
is called when std::invoke
executes, which passes that rvalue to threadFunction<1>
.
On the other hand
std::thread t2(threadFunction<1>, std::ref(obj)); // <-- calls only copy
The thread stores a std::reference_wrapper
here instead of copying the object, so the constructor doesn't get called on the main thread.
However, eventually, std::invoke
is called and calls the copy constructor, as the result of the wrapped reference being passed to the function.
If you wanted to prevent copying entirely (at least for the second thread being started) you could write something like:
std::thread t2([obj = std::move(obj)] {
threadFunction<1>(std::move(obj));
});
This would result in two moves, and would mean that obj
can go out of scope on the main thread before t2
has started.
If that's not a safety issue, you could write:
std::thread t2([&obj] {
threadFunction<1>(std::move(obj));
});
This results in only one move.