09 July 2023

C++ 核心指南目录

“Do not use capturing lambdas that are coroutines”

理由

捕获模式,对于普通 lambda 来说正确的情况,对于协程 lambda 来说问题很严重。应用协程的时候,在 lambda 捕获变量的话,会导致,在第一个悬挂点后,访问释放的变量。对于引用计数的智能指针、可复制的类型来说,都可能存在这个问题。

捕获对象的 lambda 会给闭包对象分配存储。通常是在栈上。这些存储会因为被捕获的对象离开了作用域范围,而失效。当这些被捕获的对象离开作用域范围之后,闭包对象也会离开作用域范围。

普通 lambda 这个时候会结束运行,所以并无太多问题。而协程 lambda 可能会从悬挂点回复继续执行,这时候如果闭包对象已经被析构了,访问这些对象就会导致访问已释放的内存错误。

坏例子

int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
  const auto lambda = [value, sharedFoo]() -> std::future<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

好些的例子

通过值传递给 lambda。

int value = get_value();
std::shared_ptr<Foo> sharedFoo = get_foo();
{
  // take as by-value parameter instead of as a capture
  const auto lambda = [](auto sharedFoo, auto value) -> std::future<void>
  {
    co_await something();
    // sharedFoo and value are still valid at this point
  };
  lambda(sharedFoo, value);
} // the lambda closure object has now gone out of scope

最好的例子

用函数做协程。

std::future<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);
}

强化

标记作为协程的 lambda, 且捕获列表不是空的情况。