什么时候在 C++11 lambda 的定义中需要明确命名的变量捕获?

When are explicitly-named variable captures necessitated in the definition of a C++11 lambda?

到目前为止,在查看 C++ lambda expressions in the c++11 style 时,我将它们全部分为两大类:捕获和非捕获。

非捕获 lambda,虽然在编写方式上受到更多限制,但在使用方式上却灵活得多——它们可以是 implicitly converted to analogous function-pointer types; they don’t encourage gratuitous std::function<…> usage, their implementation scope is less likely to creep out and cause side-effect problems,等等。

然而,捕获 lambda 可以用更广泛的方式编写。不可否认,它们并没有赋予我刚才提到的所有这些好处。但是捕获 lambda 弥补了它可以解决的大量问题,它打破了堆栈的前馈函数调用 DAG 的限制,具有对周围范围的多种访问形式。

然而,就我的理解而言,这是如此。当我使用捕获 lambda 表达式时,当我需要一到两个变量时,我倾向于显式引用捕获特定变量:

using lambda_t = std::function<std::add_pointer_t<void>(int)>;

lambda_t lambda_explicit = [&one, &another](int descriptor) {
    return ::mmap(nullptr, one, PROT_READ, MAP_PRIVATE, descriptor, another);
};

…如果有两个以上,我更喜欢(出于同等的语法强迫症和懒惰)避免明确命名的捕获,以支持对所有内容的引用形式:

lambda_t lambda_everything = [&](int descriptor) {
    return ::mmap(nullptr, one, PROT_READ, MAP_PRIVATE, descriptor, another);
};

... 请注意,更改 lambda 捕获的形式不会改变任何关于 lambda 类型的明显信息——例如,调用签名是相同的。这是违反直觉的,因为 much of the way capturing works is vaguely specified and somewhat implementation-specific 似乎与捕获表达式提供的详细形式多样性成反比(或者它是声明?还是声明列表?……我不确定)的全部荣耀你会看到,如果你转到最后一个 link 并向下滚动一点点。

我什至没有触及绝大多数可能性——我几乎总是只做其中之一:

  1. 根本没有捕获;
  2. 一个或两个明确命名的变量,通过引用捕获;或
  3. 不分青红皂白的按引用捕获一切:[&]

在什么情况下我应该特意使用一种捕获方式而不是另一种捕获方式?

哪些形式是特例,一般应避免?哪些有明显的惩罚——在性能、代码大小、潜在的 UB 或其他方面?任何捕获形式是否具有切实的 and/or 简单好处?

What are the circumstances in which I should go out of the way to use one form of capturing over the other?

当您仅限于使用无状态函数时,捕获不是一种选择,并且您仅限于 1。将回调注册到 C 语言时通常是这种情况API。

当你想将一个对象移动到一个捕获中时,你必须将它移动到一个命名的捕获中,因此被限制为 2。但这在 C++11 中是不可能的;您需要从 C++14 捕获的广义 lambda。广义捕获对于具有不需要与外部通信的初始状态的可变 lambda 也很有用(除非明确返回)。

此外,当您希望通过值捕获一组变量并通过引用捕获另一组变量时,默认情况下您最多可以捕获一组,并且必须至少显式捕获一组。也就是说,这可能是一个特例。我不记得曾经需要这个。

我认为没有必要使用默认捕获,但它可以节省大量冗余输入。

我更喜欢使用命名捕获,原因与我不喜欢全局变量的原因相同。它为 lambda 代码提供了范围。

如果有许多变量并且许多 lambda 仅使用未命名语法访问部分变量,那么很快就无法审核代码。好吧,如果你有一个带有 3 个局部变量的小函数,那么很容易接受 lambda 可以看到所有三个变量,但是如果你有 10 个局部变量,你将在重新访问代码试图理解是哪三个变量时浪费时间lambda 实际上修改了。