在嵌套的 lambda 中捕获通用的可调用对象 - 总是向前?
Capturing generic callable objects in nested lambdas - always forward?
我的代码库中有各种函数,它们采用通用可调用对象并在调用它之前将其传递给一系列嵌套的 lambda。示例:
template <typename TF>
void interface(TF&& f)
{
nested0([/*...*/]()
{
nested1([/*...*/](auto& x)
{
nested2([&x, /*...*/]()
{
f(x);
});
});
});
}
请注意,interface
通过转发引用 (以前称为通用引用) 获取类型 TF
的可调用对象。可调用对象通常是一个具有各种捕获变量的 lambda,包括按值和按引用。
在保持正确性的同时在嵌套 lambda 中捕获 f
的最佳(就性能而言)方法是什么?
我能想到三个方案:
复制捕获f
。
nested0([f]()
{
nested1([f](auto& x)
{
nested2([&x, f]()
{
f(x);
});
});
});
可能会导致不必要的复制,如果捕获的对象是 mutable
,则可能会导致不正确的行为。
通过引用捕获 f
。
nested0([&f]()
{
nested1([&f](auto& x)
{
nested2([&x, &f]()
{
f(x);
});
});
});
看起来很合理,但如果任何嵌套的 lambda 执行的操作比 f
的所有者还长,则可能会导致问题。想象一下,如果 nested2
的主体在一个单独的线程中执行 - f
可能已经超出范围。
制作 lambdas mutable
并通过完美转发捕获。
#define FWD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
nested0([f = FWD(f)]() mutable
{
nested1([f = FWD(f)](auto& x) mutable
{
nested2([&x, f = FWD(f)]() mutable
{
f(x);
});
});
});
lambda 必须是 mutable
,因为我们可能会将 f
从一个 lambda 移动到另一个。这种方法似乎避免了不必要的复制,并在需要比原始调用者长寿时正确移动可调用对象。
选项 3 总是最好的吗,还是它有任何潜在的缺点? ...或者可能根本没有 "best and correct" 方法 (需要有关可调用对象的知识)?
如评论中所述,鉴于此问题的上下文如此糟糕,很难说。
也就是说,在我看来,最好的解决方案是通过引用捕获所有内容,并在您需要副本时打破此逻辑,其目的是超过 f
的生命周期,例如:
nested0([&f]() {
n1([&f](auto& x) {
n2([&x,
// get a local copy of f
f{std::forward<TF>(f)}]() {
f(x);
});
});
});
反正这样的问题没有经验
最佳 解决方案可能与实际问题紧密相关。
照常。很公平。
我的代码库中有各种函数,它们采用通用可调用对象并在调用它之前将其传递给一系列嵌套的 lambda。示例:
template <typename TF>
void interface(TF&& f)
{
nested0([/*...*/]()
{
nested1([/*...*/](auto& x)
{
nested2([&x, /*...*/]()
{
f(x);
});
});
});
}
请注意,interface
通过转发引用 (以前称为通用引用) 获取类型 TF
的可调用对象。可调用对象通常是一个具有各种捕获变量的 lambda,包括按值和按引用。
在保持正确性的同时在嵌套 lambda 中捕获 f
的最佳(就性能而言)方法是什么?
我能想到三个方案:
复制捕获
f
。nested0([f]() { nested1([f](auto& x) { nested2([&x, f]() { f(x); }); }); });
可能会导致不必要的复制,如果捕获的对象是
mutable
,则可能会导致不正确的行为。通过引用捕获
f
。nested0([&f]() { nested1([&f](auto& x) { nested2([&x, &f]() { f(x); }); }); });
看起来很合理,但如果任何嵌套的 lambda 执行的操作比
f
的所有者还长,则可能会导致问题。想象一下,如果nested2
的主体在一个单独的线程中执行 -f
可能已经超出范围。制作 lambdas
mutable
并通过完美转发捕获。#define FWD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__) nested0([f = FWD(f)]() mutable { nested1([f = FWD(f)](auto& x) mutable { nested2([&x, f = FWD(f)]() mutable { f(x); }); }); });
lambda 必须是
mutable
,因为我们可能会将f
从一个 lambda 移动到另一个。这种方法似乎避免了不必要的复制,并在需要比原始调用者长寿时正确移动可调用对象。
选项 3 总是最好的吗,还是它有任何潜在的缺点? ...或者可能根本没有 "best and correct" 方法 (需要有关可调用对象的知识)?
如评论中所述,鉴于此问题的上下文如此糟糕,很难说。
也就是说,在我看来,最好的解决方案是通过引用捕获所有内容,并在您需要副本时打破此逻辑,其目的是超过 f
的生命周期,例如:
nested0([&f]() {
n1([&f](auto& x) {
n2([&x,
// get a local copy of f
f{std::forward<TF>(f)}]() {
f(x);
});
});
});
反正这样的问题没有经验
最佳 解决方案可能与实际问题紧密相关。
照常。很公平。