cppcoreguidelines-avoid-capturing-lambda-coroutines

标记具有非空捕获列表的 C++20 协程 lambda,这些 lambda 可能导致使用后释放错误,并建议避免捕获或确保 lambda 闭包对象具有保证的生存期。

此检查实现了来自 C++ 核心准则的 CP.51

使用具有非空捕获列表的协程 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 对象定义了两个捕获:value 和 sharedFoo。当调用 lambda() 时,lambda 对象在堆栈上创建,捕获的变量被复制到闭包对象中。当协程挂起时,lambda 对象超出范围,闭包对象被销毁。当协程恢复时,捕获的变量可能已被销毁,从而导致使用后释放的错误。

总之,当协程在闭包对象被销毁后恢复时,使用具有非空捕获列表的协程 lambda 会导致使用后释放的错误。此检查通过标记具有非空捕获列表的 C++20 协程 lambda 并建议避免捕获或确保 lambda 闭包对象具有保证的生存期来帮助防止此类错误。

遵循这些准则可以帮助确保在 C++ 代码中安全可靠地使用协程 lambda。