短的例子:
#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。这应该是标准的)
如果你检查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
你的代码几乎相当于:
#include <iostream>
class unnamed1
{
int& n;
public:
unnamed1(int& N) : n(N) {}
/* OK. Your this is const but you don't modify the "n" reference,
but the value pointed by it. You wouldn't be able to modify a reference
anyway even if your operator() was mutable. When you assign a reference
it will always point to the same var.
*/
void operator()() const {n = 10;}
};
class unnamed2
{
int n;
public:
unnamed2(int N) : n(N) {}
/* OK. Your this pointer is not const (since your operator() is "mutable" instead of const).
So you can modify the "n" member. */
void operator()() {n = 20;}
};
class unnamed3
{
int n;
public:
unnamed3(int N) : n(N) {}
/* BAD. Your this is const so you can't modify the "n" member. */
void operator()() const {n = 10;}
};
int main()
{
int n;
unnamed1 u1(n); u1(); // OK
unnamed2 u2(n); u2(); // OK
//unnamed3 u3(n); u3(); // Error
std::cout << n << "\n"; // "10"
}
因此,您可以将lambdas视为生成一个带有operator()的类,该类默认为const,除非您说它是可变的。
您还可以将[]中捕获的所有变量(显式或隐式)视为该类的成员:[=]对象的副本或[&]对象的引用。它们在声明lambda时被初始化,就像有一个隐藏的构造函数一样。
我也想知道为什么[=]需要显式可变的最简单的解释是在这个例子中:
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; };
在我的印象中,按值捕获的全部意义在于允许用户更改临时对象——否则我几乎总是使用按引用捕获更好,不是吗?
问题是,它是“几乎”吗?一个常见的用例似乎是返回或传递lambdas:
void registerCallback(std::function<void()> f) { /* ... */ }
void doSomething() {
std::string name = receiveName();
registerCallback([name]{ /* do something with name */ });
}
我认为mutable不是almost的例子。我认为“按值捕获”就像“允许我在捕获的实体死亡后使用它的值”,而不是“允许我更改它的副本”。但这或许是有争议的。
为了扩展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.