前言
近来,在使用 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