我偶尔会想出这样的代码:
// lazy lambda:
[&] {
// use references to a, b, c
}
// pedantic lambda
[&a, &b, &c] {
// use references to a, b, c
}
我想知道根据C ++ 14标准(或更高版本)和编译器的实际经验,哪些lambda在性能和可执行大小方面更好。
答案 0 :(得分:5)
这个例子没有区别。编译器只捕获lambda中显式引用的变量,使得延迟捕获等效于显式捕获。
懒惰捕捉稍微方便一些。更改lambda的主体以使用其他捕获的变量,或从现有lambda中删除对捕获的变量的所有引用,通常还需要更新显式捕获列表。通过延迟捕获,编译器将为您完成所有操作。
答案 1 :(得分:3)
除了你明确捕捉lambda中没有提到的东西的琐碎案例之外,还有两个可能存在差异的极端情况。
首先,隐式捕获通常不会捕获没有使用过的实体(但请参阅下一项的例外情况)。除其他外,这包括未评估的操作数中提到的内容,例如decltype
和sizeof
的操作数,以及在某些上下文中使用时的某些const
和constexpr
局部变量(请参阅[basic.def.odr]了解完整的规则):
void f(int){}
void f2(const int &) {}
void t() {
const int x = 8;
constexpr double y = 8;
const double z = 8;
auto g = []{ f(x); }; // OK
auto g1 = [=]{ f(x); }; // OK, does not capture x
// usually won't fire, though not guaranteed
static_assert(sizeof(g) == sizeof(g1), "!!");
auto g2 = []{ f(y); }; // OK
auto g3 = []{ f(z); }; // Error, must capture z
auto g4 = []{ f2(x); }; // Error, must capture x since it's odr-used
auto g5 = [=]{ f2(x); }; // OK, captures x
auto g6 = []{ f2(+x); }; // OK, doesn't odr-use x
auto g7 = []{ f2(y); }; // OK
}
如果您手动编写捕获列表,则可能会捕获超出技术需要的内容,因为管理使用或不使用内容的规则非常复杂。
其次,通用lambda中的隐式捕获将捕获依赖表达式中使用的内容,即使它们不一定是使用过的,也是为了理智。借用an example from the standard:
void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
const int x = 17;
auto g2 = [=](auto a) {
int selector[sizeof(a) == 1 ? 1 : 2]{};
f(x, selector); // OK: is a dependent expression, so captures x
// Will actually odr-use x only if sizeof(a) == 1
};
}
但是,在编写通用lambda时,实际上并不需要捕获可能或者可能不会使用的东西;如果你实例化odr-使用该东西的函数调用操作符的特化,你只需要捕获它。因此,如果您从未以某种方式调用生成的通用lambda,那么隐式捕获可能会捕获超过您所需的最小值。例如,允许这样做:
void test() {
const int x = 17;
auto g3 = [](auto a) { // no capture
int selector[sizeof(a) == 1 ? 1 : 2]{};
f(x, selector); // OK for now
};
}
只要您不使用大小为1的任何内容调用g3
:g3(0)
在典型系统上就可以了; g3('\0')
不是。
答案 2 :(得分:1)
与上面提到的人一样,生成的代码没有区别。但是,我相信,最好是明确的 - 它提供更好的可读性(更容易推理变量生命周期)并且还为编译器提供更好的支持,当您访问变量时产生错误并不意味着
我相信'懒惰捕捉'根本不是一个好的功能。