异步调用中c++ lambda表达式捕获指针为什么会有问题?

今天在异步调用中写C++ lambda时遇到一个有意思的问题,也有可能是我对lamdba的理解还不够透彻。先记在这里,希望有大神有时间解答一下。或者等…
关注者
16
被浏览
5,360
登录后你可以
不限量看优质回答 私信答主深度交流 精彩内容一键收藏

这是在使用 coroutine 时非常经典的错误,经典到 C++ Core Guidelines 都单独为它列出了一个条目

简单来说就是 lambda 返回的 coroutine frame 的生命周期通常比 lambda 对象长。而 coroutine frame 中保存的又是 lambda 的对象指针,这样在原始的 lambda 对象析构后再访问捕获的变量就出了问题。

int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
  const auto lambda = [value, sharedFoo]() -> task<void>
    co_await something();
    // "sharedFoo" and "value" have already been destroyed
    // the "shared" pointer didn't accomplish anything
  lambda();
} // the lambda closure object has now gone out of scope

解决方案也非常简单粗暴,一律都用值传参,这样 coroutine frame 就直接按值保存各参数,生命周期不会出差错。为了减少误用的隐患,C++ Core Guidelines 推荐使用普通 coroutine 函数代替 coroutine lambda

task<void> Class::do_something(int value, std::shared_ptr<Foo> sharedFoo)
  co_await something();
  // sharedFoo and value are still valid at this point
void SomeOtherFunction()
  int value = get_value();
  std::shared_ptr<Foo> sharedFoo = get_foo();
  do_something(value, sharedFoo);

上述的解决方案可以完美应对大部分情况,然而如果对 coroutine lambda 的接口有限制,比如作为参数传入另一个函数,那么这种方案就不可行了。

void bar(auto &&f) {
    f(1);
bar([value](int i) -> task<void> {
    co_await something();

解决方法同样比较简单,那就是在第一个暂停点之前复制一下捕获的变量,这样复制的变量同样会被保存在 coroutine frame 上,之后都使用这个复制的变量。

bar([value](int i) -> task<void> {
    auto copied_value = value;