将“const Whatever &”捕获到异步 lambda 中是否不安全?

Is capturing a `const Whatever &` into an asynchronous lambda unsafe?

提问人:Jeremy Friesner 提问时间:7/28/2023 最后编辑:Jeremy Friesner 更新时间:7/28/2023 访问量:59

问:

我有一些使用 lambda 的异步代码,我怀疑这有问题。问题的一个例子在这里(注意:伪代码,但真正的代码是基于JUCE的线程API):

// SUSPECT CODE (?)
void MessageReceived(const Message & msgRef) {
    Threading(this).runAsync([&, msgRef] {
        msgRef.Print();  // or whatever
    });
}

特别是,我认为从 lambda 体内部使用是不安全的,因为虽然 lambda 机制复制了 ,但引用所引用的实际对象没有被复制,因此在异步 lambda 回调例程实际执行时很可能是一个悬空引用。msgRefconst Message &MessagemsgRef

我的问题是,我上面的推理正确吗?如果没有,我错过了什么?

另外,以下代码是否足以避免该问题?在本例中,我的函数创建对象的本地副本 (),而 lambda-callback 则引用该副本。我相信 lambda 捕获机制会制作自己的私有副本,当异步例程访问它时,保证它是有效的。localCopyMessagelocalCopy

// FIXED CODE (?)
void MessageReceived(const Message & msgRef) {
    Message localCopy = msgRef;
    Threading(this).runAsync([&, localCopy] {
       localCopy.Print();  // or whatever
    });
}
C++ 异步 lambda

评论

0赞 Nathan Pierson 7/28/2023
在第一个版本中,我看不出如何有足够的关于任何引用的生命周期的信息来知道悬空引用是否可能。第二个版本看起来保证会导致悬空引用,除非在调用完成之前不返回,这似乎会破坏异步调用它的目的。msgRefMessageReceivedrunAsync
2赞 HolyBlackCat 7/28/2023
按值捕获引用将复制引用目标,而不是引用本身。
0赞 Jeremy Friesner 7/28/2023
@NathanPierson假设它引用了正在调用的函数的局部变量,因此将在返回后不久(并且可能在 lambda 例程执行之前)被销毁msgRefMessageReceived()MessageReceived()
1赞 user17732522 7/28/2023
捕获引用副本意味着闭包类型将包含从捕获的引用初始化的引用类型的成员。 在lambda中,它不是它外部所指的对象,而是它的副本。与 相同。这样你就有三个对象:lambda 外部引用的对象、副本和 lambda 内部的副本。msgRefmsgReflocalCopymsgReflocalCopy
1赞 chrysante 7/28/2023
两个示例中的默认捕获都不起作用,可以省略。&

答:

1赞 pptaszni 7/28/2023 #1

你的第一个简短例子是正确的。在默认的按引用捕获之后,您可以显式捕获按副本捕获 - 也就是说,lambda 保存引用对象的副本。msgRef

[expr.prim.lambda.捕获]

如果出现以下情况,则通过副本捕获实体

(10.2) — 使用非 this、& identifier 或 & identifier 形式的捕获显式捕获 初始 化。

(...)

如果实体是对对象的引用,则此类数据成员的类型是引用类型,如果实体是对函数的引用,则对引用的函数类型的左值引用,否则是相应捕获实体的类型

后来标准给出了这个例子:

[&, i]{ }; // OK

这是假设在您的正文中,您没有任何自动变量,这些变量将默认捕获,然后在 lambda 正文中意外误用。MessageReceived&