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?
Lambdas always capture objects, and they can do so by value or by reference.
The capture clause is used to (indirectly) give a lambda access to variables available in the surrounding scope that it normally would not have access to. All we need to do is list the entities we want to access from within the lambda as part of the capture clause.
The mutable keyword is used so that the body of the lambda expression can modify its copies of the external variables x and y , which the lambda expression captures by value. Because the lambda expression captures the original variables x and y by value, their values remain 1 after the lambda executes.
A lambda is also just a function object, so you need to have a () to call it, there is no way around it (except of course some function that invokes the lambda like std::invoke ). If you want you can drop the () after the capture list, because your lambda doesn't take any parameters.
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
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