前言
近来,在使用 C++ 写代码的时候经常会使用 Lambda 作为回调函数进行使用,在使用的过程中发现有些东西没弄懂。
就比如今天要说的,在 Lambda 中使用值捕获的情况下,捕获成员变量和局部变量是否一样,在 Lambda 内修改是否会影响外面的变量。
废话不多说,下面开始。
捕获局部变量(按值捕获)
1
2
3
4
5
6
7
8
9
10
11
12
|
int main(int argc, char *argv[])
{
int i = 1;
auto f = [=]() mutable {
std::cout << "before lambda: " << i << std::endl;
i = 2;
std::cout << "after lambda: " << i << std::endl;
};
f();
std::cout << "outer lambda: " << i << std::endl;
return 0;
}
|
在上面的例子中,写了一个名为 f 的 lambda 表达式,在 f 中,使用了值捕获,捕获了 局部变量 i ,它的值为 1 。
由于我们需要修改捕获到的 i ,所以需要加上 mutable ,在 f 中把 i 的值改为了 2 ,然后分别打印了修改之前和修改之后的值。
最后在调用完 f 之后再次打印了 i 的值,结果如下所示
1
2
3
|
before lambda: 1
after lambda: 2
outer lambda: 1
|
从结果中可以看到,我们的确是修改了 f 中的 i 的值,但是修改对外部变量是无效的,这就是值捕获,会默认拷贝一份。
我们可以把它们的地址打印出来看一下
1
2
3
4
5
6
7
8
9
10
|
int main(int argc, char *argv[])
{
int i = 1;
auto f = [=]() mutable {
std::cout << "pointer lambda: " << &i << std::endl;
};
f();
std::cout << "pointer outer: " << &i << std::endl;
return 0;
}
|
结果如下
1
2
|
pointer lambda: 0x16aed7188
pointer outer: 0x16aed718c
|
可以看到它们是不同的地址,所以修改了里面的不会影响外面的。
捕获成员变量(按值捕获)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class Lambda
{
public:
Lambda():i(1){}
virtual ~Lambda(){}
void func() {
auto l = [=]() {
std::cout << "before lambda: " << i << std::endl;
i = 2;
std::cout << "after lambda: " << i << std::endl;
};
l();
std::cout << "outer lambda: " << i << std::endl;
}
private:
int i;
};
int main(int argc, char *argv[])
{
Lambda lambda;
lambda.func();
return 0;
}
|
在这个例子中,我们用值捕获,捕获获了成员变量 i ,然后对其修改,结果如下
1
2
3
|
before lambda: 1
after lambda: 2
outer lambda: 2
|
与上次不同的是,这次对 i 的修改影响到了 lambda 之外。
之所以这里会影响到 lambda 之外,是因为这里其实是隐式捕获了 this ,所以这里的 i 其实是 this->i 。
到这里就明白了, Lambda 在按值捕获成员变量的时候其实捕获的是 this 指针,而不是把成员变量拷贝一份,所以在 Lambda 中对成员变量的修改会反应到对象上。
同样来打印一下 i 的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Lambda
{
public:
Lambda():i(1){}
virtual ~Lambda(){}
void func() {
auto l = [=]() {
std::cout << "pointer lambda: " << &i << std::endl;
};
l();
std::cout << "pointer outer: " << &i << std::endl;
}
private:
int i;
};
int main(int argc, char *argv[])
{
Lambda lambda;
lambda.func();
return 0;
}
|
结果如下
1
2
|
pointer lambda: 0x16d6eb188
pointer outer: 0x16d6eb188
|
可以看到这里的指针,指向了同一个地址,这也就证明了在 lambda 中修改成员变量会反应到对象中。
总结
- 按值捕获局部变量不会修改局部变量的值,而是会拷贝一份,修改的是拷贝的值。且要修改需要在
lambda 中加 mutable 才可修改
- 按值捕获成员变量,其实捕获的是
this 指针,在 Lambda 中修改成员变量的值会反应到对象上。
- 值捕获都会拷贝一份变量,不同的是局部变量拷贝的是变量本身,而成员变量拷贝的是对象的指针。
参考
Lambda expressions (since C++11) - cppreference.com