前言

近来,在使用 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;
}

在上面的例子中,写了一个名为 flambda 表达式,在 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 中修改成员变量会反应到对象中。

总结

  1. 按值捕获局部变量不会修改局部变量的值,而是会拷贝一份,修改的是拷贝的值。且要修改需要在 lambda 中加 mutable 才可修改
  2. 按值捕获成员变量,其实捕获的是 this 指针,在 Lambda 中修改成员变量的值会反应到对象上。
  3. 值捕获都会拷贝一份变量,不同的是局部变量拷贝的是变量本身,而成员变量拷贝的是对象的指针。

参考

Lambda expressions (since C++11) - cppreference.com