短的例子:
#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。这应该是标准的)
为了扩展Puppy的回答,lambda函数被设计为纯函数。这意味着给定唯一输入集的每次调用总是返回相同的输出。让我们将input定义为调用lambda时所有参数加上所有捕获变量的集合。
在纯函数中,输出完全依赖于输入,而不依赖于某些内部状态。因此,任何lambda函数,如果是纯的,不需要改变其状态,因此是不可变的。
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.
如果你检查lambda的3个不同用例,你可能会发现区别:
按值捕获参数
使用'mutable'关键字按值捕获参数
通过引用捕获参数
案例1:
当你通过值捕获一个参数时,会发生一些事情:
不允许修改lambda内部的参数
无论lambda是什么,参数的值都保持不变
调用时,不管调用时的参数值是什么。
例如:
{
int x = 100;
auto lambda1 = [x](){
// x += 2; // compile time error. not allowed
// to modify an argument that is captured by value
return x * 2;
};
cout << lambda1() << endl; // 100 * 2 = 200
cout << "x: " << x << endl; // 100
x = 300;
cout << lambda1() << endl; // in the lambda, x remain 100. 100 * 2 = 200
cout << "x: " << x << endl; // 300
}
Output:
200
x: 100
200
x: 300
案例2:
在这里,当您通过值捕获参数并使用'mutable'关键字时,与第一种情况类似,您创建了该参数的“副本”。这个“副本”存在于lambda的“世界”中,但是现在,你实际上可以在lambda世界中修改参数,因此它的值会被改变,并保存,以便在将来调用这个lambda时引用。同样,参数的外部“生命”可能完全不同(就值而言):
{
int x = 100;
auto lambda2 = [x]() mutable {
x += 2; // when capture by value, modify the argument is
// allowed when mutable is used.
return x;
};
cout << lambda2() << endl; // 100 + 2 = 102
cout << "x: " << x << endl; // in the outside world - x remains 100
x = 200;
cout << lambda2() << endl; // 104, as the 102 is saved in the lambda world.
cout << "x: " << x << endl; // 200
}
Output:
102
x: 100
104
x: 200
案例3:
这是最简单的情况,因为x不再有两次生命。现在x只有一个值,它在外部世界和lambda世界之间共享。
{
int x = 100;
auto lambda3 = [&x]() mutable {
x += 10; // modify the argument, is allowed when mutable is used.
return x;
};
cout << lambda3() << endl; // 110
cout << "x: " << x << endl; // 110
x = 400;
cout << lambda3() << endl; // 410.
cout << "x: " << x << endl; // 410
}
Output:
110
x: 110
410
x: 410
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 new const 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 keyword mutable, the captured entity will became a non-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.