CppCoreGuidelines CP.51 协程的 lambda 中不要进行捕获
09 July 2023
“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, 且捕获列表不是空的情况。