Reputation: 3197
In the following code:
#include <iostream>
#include <thread>
using namespace std;
class tester {
public:
tester() {
cout << "constructor\t" << this << "\n";
}
tester(const tester& other) {
cout << "copy cons.\t" << this << "\n";
}
~tester() {
cout << "destructor\t" << this << "\n";
}
void print() const {
cout << "print\t\t" << this << "\n";
}
};
int main() {
tester t;
cout << " before lambda\n";
thread t2([=] {
cout << " thread start\n";
t.print();
cout << " thread end\n";
});
t2.join();
cout << " after join" << endl;
return 0;
}
When compiled with cl.exe
(on Windows) I get the following:
constructor 012FFA93
before lambda
copy cons. 012FFA92
copy cons. 014F6318
destructor 012FFA92
thread start
print 014F6318
thread end
destructor 014F6318
after join
destructor 012FFA93
And with g++
(on WSL) I get:
constructor 0x7ffff5b2155e
before lambda
copy cons. 0x7ffff5b2155f
copy cons. 0x7ffff5b21517
copy cons. 0x7fffedc630c8
destructor 0x7ffff5b21517
destructor 0x7ffff5b2155f
thread start
print 0x7fffedc630c8
thread end
destructor 0x7fffedc630c8
after join
destructor 0x7ffff5b2155e
I would expect that the [=]
capture would create exactly 1 copy of tester
. Why are there several copies that are immediately destroyed?
Why the divergence between MSVC and GCC? Is this undefined behavior or something?
Upvotes: 6
Views: 275
Reputation: 41090
The standard requires that the callable passed to the constructor for std::thread
is effectively copy-constructible ([thread.thread.constr])
Mandates: The following are all true:
is_constructible_v<decay_t<F>, F>
- [...]
is_constructible_v<decay_t<F>, F>
is the same as is_copy_constructible
(or rather, it's the other way around).
This is to allow implementations to freely pass around the callable until it reaches the point where it gets invoked. (In fact, the standard itself suggests the callable is copied at least once.)
Since a lambda is compiled into a small class with the function call operator overloaded (a functor), each time your lambda gets copied, it will create a copy of the captured tester
instance.
If you do not wish for copying to happen, you can take a reference to your instance in the capture list instead:
thread t2([&ref = t] {
cout << " thread start\n";
ref.print();
cout << " thread end\n";
});
constructor 0x7ffdfdf9d1e8
before lambda
thread start
print 0x7ffdfdf9d1e8
thread end
after join
destructor 0x7ffdfdf9d1e8
Upvotes: 3