Reputation: 199
This seems like a compiler bug, but it occurs in both gcc and clang (and maybe others).
If a function takes a lambda f
and it creates a thread from another lambda g
which calls f
, f
can quietly lose access to its captured variables. Here's a demonstration.
#include <thread>
#include <iostream>
using std::cout;
using std::endl;
using std::thread;
template<typename F>
__attribute__((noinline)) // inlining hides the bug
thread* callMeLater1(F f)
{
return new thread(
[&]() {
// ...
// Insert sleep logic.
// ...
f();
});
}
template<typename F>
__attribute__((noinline))
thread* callMeLater2(F f)
{
return new thread(
[f]() {
// ...
// Insert sleep logic.
// ...
f();
});
}
int main(int argc, const char * argv[]) {
int a = 42;
cout << "orign: " << ((void*) &a) << endl;
auto f = [&]() {
cout << "later: " << ((void*) &a) << endl;
};
thread* t1 = callMeLater1(f);
t1->join();
delete t1;
thread* t2 = callMeLater2(f);
t2->join();
delete t2;
return 0;
}
Here are the results.
orign: 0x7ffee88727ac
later: 0
later: 0x7ffee88727ac
The call to f
inside callMeLater1
is corrupted. f
's accesses to a
will be broken, causing silent bugs or segfaults. When f
is called inside callMeLater2
, which doesn't use default capture &
, the function works correctly.
Is this expected?
Upvotes: 0
Views: 45
Reputation: 118300
This is undefined behavior.
The lambda captures, by reference, the parameter to callMeLater1
. This parameter gets destroyed, of course, as soon as callMeLater1
returns.
Nothing guarantees you that the new execution thread invokes the passed-in closure before callmeLater1
returns. As such, the passed-in callable object can get destroyed before the new thread attempts to invoke it, resulting in undefined behavior.
Upvotes: 2