当另一个调用lambda使用默认捕获时,可以破坏lambda的闭包吗?

时间:2019-01-26 00:37:03

标签: c++

这似乎是编译器错误,但同时发生在gcc和clang(可能还有其他)中。

如果一个函数接受一个lambda f并从另一个调用g的lambda f创建一个线程,则f可以悄悄地失去对其捕获变量的访问权限。这是一个示范。

#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;
}

这是结果。

orign: 0x7ffee88727ac
later: 0
later: 0x7ffee88727ac

f内部对callMeLater1的调用已损坏。 fa的访问将被破坏,从而导致无提示的错误或段错误。当在f内部调用callMeLater2时,不使用默认捕获&时,该函数将正常工作。

这是预期的吗?

1 个答案:

答案 0 :(得分:2)

这是未定义的行为。

lambda通过引用捕获callMeLater1的参数。当然,callMeLater1返回后,此参数将被销毁。

没有任何东西可以保证您新的执行线程在callmeLater1返回之前调用传入的闭包。这样,传入的可调用对象可能在新线程尝试调用它之前被销毁,从而导致不确定的行为。