为什么 C++11 的 lambda 需要“可变”默认情况下按值捕获的关键字?
简短示例:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
问题:为什么我们需要 mutable
关键字?它与传递给命名函数的传统参数有很大不同。背后的原理是什么?
我的印象是,按值捕获的全部要点是允许用户更改临时值 - 否则我几乎总是使用按引用捕获更好,不是吗?
有什么启示吗?
(顺便说一下,我正在使用 MSVC2010。据我所知,这应该是标准的)
Short example:
#include <iostream>
int main()
{
int n;
[&](){n = 10;}(); // OK
[=]() mutable {n = 20;}(); // OK
// [=](){n = 10;}(); // Error: a by-value capture cannot be modified in a non-mutable lambda
std::cout << n << "\n"; // "10"
}
The question: Why do we need the mutable
keyword? It's quite different from traditional parameter passing to named functions. What's the rationale behind?
I was under the impression that the whole point of capture-by-value is to allow the user to change the temporary -- otherwise I'm almost always better off using capture-by-reference, aren't I?
Any enlightenments?
(I'm using MSVC2010 by the way. AFAIK this should be standard)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(13)
它需要可变的,因为默认情况下,函数对象每次调用时都应该产生相同的结果。这实际上是面向对象函数和使用全局变量的函数之间的区别。
It requires
mutable
because by default, a function object should produce the same result every time it's called. This is the difference between an object orientated function and a function using a global variable, effectively.您的代码几乎与此相同:
因此,您可以将 lambda 视为使用 operator() 生成一个类,该类默认为 const,除非您说它是可变的。
您还可以将 [] 内捕获的所有变量(显式或隐式)视为该类的成员:[=] 对象的副本或 [&] 对象的引用。当您声明 lambda 时,它们会被初始化,就像有一个隐藏的构造函数一样。
Your code is almost equivalent to this:
So you could think of lambdas as generating a class with operator() that defaults to const unless you say that it is mutable.
You can also think of all the variables captured inside [] (explicitly or implicitly) as members of that class: copies of the objects for [=] or references to the objects for [&]. They are initialized when you declare your lambda as if there was a hidden constructor.
你必须明白捕获意味着什么!它是捕获而不是参数传递!让我们看一些代码示例:
正如您所见,即使
x
已更改为20
,lambda 仍然返回 10(x
仍然是lambda 内的5
)更改 lambda 内部的 x 意味着在每次调用时更改 lambda 本身(lambda 在每次调用时都会发生变化)。为了保证正确性,标准引入了
mutable
关键字。通过将 lambda 指定为可变,您可以说每次调用 lambda 都可能导致 lambda 本身发生变化。让我们看另一个例子:上面的例子表明,通过使 lambda 可变,更改 lambda 内部的
x
会在每次调用时使用新的x
值“变异” lambda与主函数中x
的实际值无关You have to understand what capture means! it's capturing not argument passing! let's look at some code samples:
As you can see even though
x
has been changed to20
the lambda is still returning 10 (x
is still5
inside the lambda)Changing
x
inside the lambda means changing the lambda itself at each call (the lambda is mutating at each call). To enforce correctness the standard introduced themutable
keyword. By specifying a lambda as mutable you are saying that each call to the lambda could cause a change in the lambda itself. Let see another example:The above example shows that by making the lambda mutable, changing
x
inside the lambda "mutates" the lambda at each call with a new value ofx
that has no thing to do with the actual value ofx
in the main function问题是,“几乎”了吗?一个常见的用例似乎是返回或传递 lambda:
我认为
mutable
不是“几乎”的情况。我认为“按值捕获”就像“允许我在捕获的实体死亡后使用它的值”,而不是“允许我更改它的副本”。但也许这还可以争论。The question is, is it "almost"? A frequent use-case appears to be to return or pass lambdas:
I think that
mutable
isn't a case of "almost". I consider "capture-by-value" like "allow me to use its value after the captured entity dies" rather than "allow me to change a copy of it". But perhaps this can be argued.FWIW,C++ 标准化委员会的知名成员 Herb Sutter 在 Lambda 正确性和可用性问题:
他的论文是关于为什么应该在 C++14 中更改这一点。它很短,写得很好,如果您想了解“[委员会成员]对此特定功能的想法”,值得一读。
FWIW, Herb Sutter, a well-known member of the C++ standardization committee, provides a different answer to that question in Lambda Correctness and Usability Issues:
His paper is about why this should be changed in C++14. It is short, well written, worth reading if you want to know "what's on [committee member] minds" with regards to this particular feature.
您需要考虑 Lambda 函数的闭包类型是什么。每次声明 Lambda 表达式时,编译器都会创建一个闭包类型,它无非是一个带有属性的未命名类声明(声明 Lambda 表达式的环境)和函数调用
: :operator()
已实现。当您使用按值复制捕获变量时,编译器将在闭包类型中创建一个新的const
属性,因此您无法在 Lambda 表达式内更改它因为它是一个“只读”属性,这就是他们称之为“闭包”的原因,因为在某种程度上,您通过将变量从上层作用域复制到 Lambda 来关闭 Lambda 表达式范围。当您使用关键字mutable
时,捕获的实体将成为闭包类型的非常量
属性。这就是导致通过值捕获的可变变量中所做的更改不会传播到上层作用域,而是保留在有状态 Lambda 内部的原因。始终尝试想象 Lambda 表达式的最终闭包类型,这对我帮助很大,我希望它也能对您有所帮助。
You need to think what is the closure type of your Lambda function. Every time you declare a Lambda expression, the compiler creates a closure type, which is nothing less than an unnamed class declaration with attributes (environment where the Lambda expression where declared) and the function call
::operator()
implemented. When you capture a variable using copy-by-value, the compiler will create a newconst
attribute in the closure type, so you can't change it inside the Lambda expression because it is a "read-only" attribute, that's the reason they call it a "closure", because in some way, you are closing your Lambda expression by copying the variables from upper scope into the Lambda scope. When you use the keywordmutable
, the captured entity will became anon-const
attribute of your closure type. This is what causes the changes done in the mutable variable captured by value, to not be propagated to upper scope, but keep inside the stateful Lambda.Always try to imagine the resulting closure type of your Lambda expression, that helped me a lot, and I hope it can help you too.
请参阅此草案,第 5.1.2 节 [expr.prim .lambda],第 5 款:
编辑 litb 的评论:
也许他们想到了按值捕获,以便变量的外部更改不会反映在 lambda 内部?参考文献是双向的,这就是我的解释。不过不知道好不好用。
编辑 kizzx2 的评论:
lambda 最常被用作算法的函子。默认的 const 性允许它在常量环境中使用,就像可以在其中使用普通的 const 限定函数一样,但非 const 函数可以在常量环境中使用。 - 有资格的人不能。也许他们只是想让这些案例变得更直观,因为他们知道他们脑子里在想什么。 :)
See this draft, under 5.1.2 [expr.prim.lambda], subclause 5:
Edit on litb's comment:
Maybe they thought of capture-by-value so that outside changes to the variables aren't reflected inside the lambda? References work both ways, so that's my explanation. Don't know if it's any good though.
Edit on kizzx2's comment:
The most times when a lambda is to be used is as a functor for algorithms. The default
const
ness lets it be used in a constant environment, just like normalconst
-qualified functions can be used there, but non-const
-qualified ones can't. Maybe they just thought to make it more intuitive for those cases, who know what goes on in their mind. :)n
不是临时的。 n 是您使用 lambda 表达式创建的 lambda 函数对象的成员。默认期望是调用 lambda 不会修改其状态,因此它是 const 以防止您意外修改n
。n
is not a temporary. n is a member of the lambda-function-object that you create with the lambda expression. The default expectation is that calling your lambda does not modify its state, therefore it is const to prevent you from accidentally modifyingn
.如果您检查 lambda 的 3 个不同用例,您可能会发现其中的差异:
情况 1:
当您按值捕获参数时,会发生一些事情:
调用,无论调用 lambda 时的参数值是什么。
例如:
情况 2:
在这里,当您按值捕获参数并使用“mutable”关键字时,与第一种情况类似,您将创建该参数的“副本”。这个“副本”存在于 lambda 的“世界”中,但现在,您实际上可以在 lambda 世界中修改参数,因此它的值被更改并保存,并且可以在以后的调用中引用拉姆达。同样,论证的外部“生活”可能完全不同(价值方面):
情况 3:
这是最简单的情况,因为 x 不再有 2 条生命。现在 x 只有一个值,并且在外部世界和 lambda 世界之间共享。
You might see the difference, if you check 3 different use cases of lambda:
case 1:
When you capture an argument by value, a few things happen:
called, not matter what will be the argument value at the time the lambda is called.
so for example:
case 2:
Here, when you capture an argument by value and use the 'mutable' keyword, similar to the first case, you create a "copy" of this argument. This "copy" lives in the "world" of the lambda, but now, you can actually modify the argument within the lambda-world, so its value is changed, and saved and it can be referred to, in the future calls of this lambda. Again, the outside "life" of the argument might be totally different (value wise):
case 3:
This is the easiest case, as no more 2 lives of x. Now there is only one value for x and it's shared between the outside world and the lambda world.
为了扩展 Puppy 的答案,lambda 函数旨在成为纯函数。这意味着给定唯一输入集的每个调用始终返回相同的输出。让我们将输入定义为调用 lambda 时所有参数加上所有捕获的变量的集合。
在纯函数中,输出仅取决于输入,而不取决于某些内部状态。因此,任何 lambda 函数(如果是纯函数)都不需要更改其状态,因此是不可变的。
当 lambda 通过引用捕获时,在捕获的变量上写入是对纯函数概念的一种压力,因为纯函数应该做的就是返回一个输出,尽管 lambda 不一定会发生变化,因为写入发生在外部变量上。即使在这种情况下,正确的用法也意味着如果再次使用相同的输入调用 lambda,则每次的输出都将相同,尽管对 by-ref 变量有这些副作用。此类副作用只是返回一些额外输入(例如更新计数器)的方法,并且可以重新表述为纯函数,例如返回元组而不是单个值。
To extend Puppy's answer, lambda functions are intended to be pure functions. That means every call given a unique input set always returns the same output. Let's define input as the set of all arguments plus all captured variables when the lambda is called.
In pure functions output solely depends on input and not on some internal state. Therefore any lambda function, if pure, does not need to change its state and is therefore immutable.
When a lambda captures by reference, writing on captured variables is a strain on the concept of pure function, because all a pure function should do is return an output, though the lambda does not certainly mutate because the writing happens to external variables. Even in this case a correct usage implies that if the lambda is called with the same input again, the output will be the same everytime, despite these side effects on by-ref variables. Such side effects are just ways to return some additional input (e.g. update a counter) and could be reformulated into a pure function, for example returning a tuple instead of a single value.
我也想知道这个问题,为什么
[=]
需要显式mutable
的最简单解释是在这个例子中:输出:
用文字:
您可以看到
x
值在第二次调用时不同(call1 为 1,call2 为 6)。复制)(如果是
[=]
)。在一般情况下,我们必须具有相同的捕获变量值,才能根据已知的捕获值具有相同的 lambda 可预测行为,而不是在 lambda 工作期间更新。这就是为什么默认行为采用 const(以预测 lambda 对象成员的更改),并且当用户意识到后果时,他会通过
mutable
自行承担此责任。与按值捕获相同。以我为例:
I also was wondering about it and the simplest explanation why
[=]
requires explicitmutable
is in this example:Output:
By words:
You can see that the
x
value is different at the second call (1 for the call1 and 6 for the call2).copy) in case of
[=]
.And in general case we have to have the same value of the captured variable to have the same predictable behavior of the lambda based on the known captured value, not updated during the lambda work. That's why the default behavior assumed
const
(to predict changes of the lambda object members) and when a user is aware of consequences he takes this responsibility on himself withmutable
.Same with capturing by value. For my example:
现在有一项提案可以减轻 lambda 声明中对
mutable
的需求:n3424There is now a proposal to alleviate the need for
mutable
in lambda declarations: n3424这实际上将两个问题混为一谈,将它们分开可以更容易地提供答案:
const
?问题#1 有一个简单的事实答案:默认情况下,lambda 捕获的值是 const。在这些情况下:
n
或val
基本上是const int
类型的 lambda 的“成员”。您无法修改类型为 const int 的类或结构的成员,因为它是 const。问题#2 是一个关于语言设计的问题,答案可能很复杂或固执己见。这里的其他答案已经给出了许多可能的答案,但其中许多都没有回答问题#1,所以我认为它们对于只对 lambda 捕获如何工作而不讨论语言设计原理和推理感兴趣的访问者没有帮助。
This really mixes up two questions into one, and separating them makes it easier to provide answers:
const
by default?Question #1 has a simple factual answer: Values captured by lambdas are
const
by default. In these cases:n
orval
are basically "members" of the lambdas of typeconst int
. You cannot modify a member of a class or struct that has typeconst int
, because it isconst
.Question #2 is a question about language design, and the answer can be complicated or opinionated. Many possible answers have been given in the other answers here already, but many of them do not answer question #1, so I think they are not helpful for visitors who are only interested in how lambda capture works, not discuss language design principles and reasoning.