短的例子:
#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。这应该是标准的)
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.
参见本草案,在5.1.2下[expr.prim.],第5款:
lambda表达式的闭包类型有一个公共内联函数调用操作符(13.5.4),其参数
和返回类型由lambda表达式的参数声明子句和trailingreturn-描述
类型分别。此函数调用操作符声明为const(9.3.1)当且仅当lambdaexpression为
Parameter-declaration-clause后面不跟mutable。
编辑litb的评论:
也许他们想到了按值捕获,这样外部对变量的更改就不会反映在lambda中?引荐是双向的,这就是我的解释。但我不知道这是否有用。
编辑kizzx2的评论:
使用lambda的大多数情况下是作为算法的函子。默认的constness允许它在常量环境中使用,就像普通的const限定函数可以在那里使用,但非const限定的函数不能。也许他们只是想让这些情况更直观,他们知道自己在想什么。:)
在我的印象中,按值捕获的全部意义在于允许用户更改临时对象——否则我几乎总是使用按引用捕获更好,不是吗?
问题是,它是“几乎”吗?一个常见的用例似乎是返回或传递lambdas:
void registerCallback(std::function<void()> f) { /* ... */ }
void doSomething() {
std::string name = receiveName();
registerCallback([name]{ /* do something with name */ });
}
我认为mutable不是almost的例子。我认为“按值捕获”就像“允许我在捕获的实体死亡后使用它的值”,而不是“允许我更改它的副本”。但这或许是有争议的。
如果你检查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
FWIW, c++标准化委员会的知名成员Herb Sutter在Lambda正确性和可用性问题中给出了不同的答案:
Consider this straw man example, where the programmer captures a local variable by
value and tries to modify the
captured value (which is a member variable of the lambda object):
int val = 0;
auto x = [=](item e) // look ma, [=] means explicit copy
{ use(e,++val); }; // error: count is const, need ‘mutable’
auto y = [val](item e) // darnit, I really can’t get more explicit
{ use(e,++val); }; // same error: count is const, need ‘mutable’
This feature appears to have been added out of a concern that the user
might not realize he got a copy, and in particular that since lambdas
are copyable he might be changing a different lambda’s copy.
他的论文是关于为什么在c++ 14中应该改变这一点。它很短,写得很好,如果你想知道关于这个特定的特性“委员们在想什么”,值得一读。
我也想知道为什么[=]需要显式可变的最简单的解释是在这个例子中:
int main()
{
int x {1};
auto lbd = [=]() mutable { return x += 5; };
printf("call1:%d\n", lbd());
printf("call2:%d\n", lbd());
return 0;
}
输出:
call1:6
call2:11
单词:
您可以看到在第二次调用时x值是不同的(call1为1,call2为6)。
lambda对象按值保存捕获的变量(有自己的值)
复制),如果[=]。
lambda可以被调用多次。
在一般情况下,我们必须有相同的捕获变量的值,基于已知的捕获值,有相同的可预测的lambda行为,而不是在lambda工作期间更新。这就是为什么默认行为假设为const(预测lambda对象成员的变化),当用户意识到后果时,他会使用mutable来承担这个责任。
与按值捕获相同。举个例子:
auto lbd = [x]() mutable { return x += 5; };