Lambda表达式之capture子句

1、基本用法

Lambda 可在其主体中引入新的变量(用 C++14),它还可以访问(或“捕获”)周边范围内的变量。 Lambda 以 capture 子句开头。 它指定捕获哪些变量,以及捕获是通过值还是通过引用进行的。 有与号 (&) 前缀的变量通过引用进行访问,没有该前缀的变量通过值进行访问。空 capture 子句 [ ] 指示 lambda 表达式的主体不访问封闭范围中的变量,只访问lambda体中提及的变量。基本用法如下面代码所示:

int main()
{
    using namespace std;
    auto p = [] {cout << "Lambda base using"; };
    p();
}

2、引用传递与值传递

可以使用默认捕获模式来指示如何捕获 Lambda 体中引用的任何外部变量:[&] 表示通过引用捕获引用的所有变量,而 [=] 表示通过值捕获它们。 可以使用默认捕获模式,然后为特定变量显式指定相反的模式;使用默认捕获时,只有 Lambda 体中提及的变量才会被捕获。 例如,如果 lambda 体通过引用访问外部变量 total 并通过值访问外部变量 factor,则以下 capture 子句等效:

[&total, factor]
[factor, &total]
[&, factor]
[=, &total]

[]:不捕获任何外部变量,只捕获Lambda体中的变量
[=]:以值捕获所有外部变量
[&]:以引用捕获所有外部变量
[var]:以值的方式捕获特定变量var
[&var]:以引用方式捕获特定变量var
[=,&var]:以值方式捕获所有外部变量,但以引用的方式捕获变量var
[&,var]:以引用方式捕获所有外部变量,但以值的方式捕获变量var

基于以上内容进行代码示例如下:

 1 int main()
 2 {
 3     using namespace std;
 4     int b = 99;
 5     string c = "test";
 6 
 7     //捕获Lambda体中的变量
 8     auto p = [] {int a = 100; cout << "a value is:" << a << endl; };
 9     p();
10 
11     //以值的方式捕获所有外部变量
12     auto p1 = [=] {cout << "b value is:" << b << endl; 
13                     cout << "c value is:" << c.c_str() << endl; };
14     p1();
15 
16     //以引用方式捕获所有外部变量
17     auto p2 = [&] {cout << "b before value is:" << b << endl; 
18                     b += 1; cout << "b after value is:" << b << endl;
19                     cout << "c before value is:" << c.c_str() << endl;
20                     c.append("abc"); cout << "c after value is:" << c.c_str() << endl; };
21     p2();
22     
23     //以值的方式捕获特定变量
24     auto p3 = [b] {cout << "b value is:" << b << endl; };
25     p3();
26 
27     //以引用方式捕获特定变量
28     auto p4 = [&b] {cout << "b before value is:" << b << endl;
29                     b += 1; cout << "b after value is:" << b << endl; };
30     p4();
31 
32     //以值方式捕获所有外部变量,但以引用的方式捕获特定变量
33     auto p5 = [=, &b] {cout << "b before value is:" << b << endl;
34                         b += 1; cout << "b after value is:" << b << endl; 
35                         cout << "c value is:" << c.c_str() << endl; };
36     p5();
37 
38     //以引用方式捕获所有外部变量,但以值的方式捕获特定变量
39     auto p6 = [&, b] {cout << "b value is:" << b << endl;
40     cout << "c before value is:" << c.c_str() << endl;
41     c.append("123"); cout << "c after value is:" << c.c_str() << endl; };
42     p6();
43 }

如果 capture 子句包含默认捕获 &,则该 capture 子句的捕获中没有任何标识符可采用 &identifier 形式。 同样,如果 capture 子句包含默认捕获 =,则该 capture 子句没有任何捕获可采用 =identifier 形式。 标识符或 this 在 capture 子句中出现的次数不能超过一次。 以下代码片段给出了一些示例:

struct S { void f(int i); };

void S::f(int i) {
    [&, i]{};      // OK
    [&, &i]{};     // ERROR: i preceded by & when & is the default
    [=, this]{};   // ERROR: this when = is the default
    [=, *this]{ }; // OK: captures this by value. See below.
    [i, i]{};      // ERROR: i repeated
}

3、this指针在Lambda表达式中的应用

 要在类成员函数体中使用 Lambda 表达式,请将 this 指针传递给 capture 子句,以提供对封闭类的成员函数和数据成员的访问权限。Visual Studio 2017 版本 15.3 及更高版本(在 /std:c++17 模式及更高版本中可用):可以通过在 capture 子句中指定 *this 通过值捕获 this 指针。 通过值捕获会将整个闭包复制到调用 Lambda 的每个调用站点。 (闭包是封装 Lambda 表达式的匿名函数对象)。当 Lambda 在并行或异步操作中执行时,通过值捕获非常有用。使用capture子句需要注意如下几点(官方):

---引用捕获可用于修改外部变量,而值捕获却不能实现此操作。 (mutable 允许修改副本,而不能修改原始项。)

 1 int main()
 2 {
 3     using namespace std;
 4     int b = 99;
 5     string c = "test";
 6 
 7     auto p = [b]()mutable { cout << "b before value is:" << b << endl;
 8     b += 1; cout << "b after value is:" << b << endl; };
 9     p();
10 
11     cout << "b current value is:" << b << endl;
12 }

---引用捕获会反映外部变量的更新,而值捕获不会。

---引用捕获引入生存期依赖项,而值捕获却没有生存期依赖项。 当 Lambda 以异步方式运行时,这一点尤其重要。 如果在异步 Lambda 中通过引用捕获局部变量,该局部变量将很容易在 Lambda 运行时消失。 代码可能会导致在运行时发生访问冲突。

this指针在Lambda表达式中的应用根据不同的C++版本对比如下:

演示代码如下:

 1 struct S {
 2     void test();
 3     int b;
 4 };
 5 void S::test()
 6 {
 7     using namespace std;
 8     b = 1;
 9     auto p = [this] {cout << "b value is:" << b << endl; };
10     p();
11 
12     b += 1;
13     auto p1 = [=, self = *this]{ cout << "b value is:" << self.b << endl; };
14     p1();
15 
16     b += 2;
17     auto p2 = [=, *this]{ cout << "b value is:" << b << endl; };
18     p2();
19 }
20 
21 int main()
22 {
23     S s;
24     s.test();
25 }

 4、capture引入并初始化新的变量

在 C++14 中,可在 Capture 子句中引入并初始化新的变量,而无需使这些变量存在于 Lambda 函数的封闭范围内。 初始化可以任何任意表达式表示;且将从该表达式生成的类型推导新变量的类型。 借助此功能,可以从周边范围捕获只移动的变量(例如 std::unique_ptr)并在 Lambda 中使用它们。

 1 int main()
 2 {
 3     //初始化pt为12个值为0的容器
 4     std::unique_ptr<vector<int>> pt = std::make_unique<vector<int>>(12);
 5 
 6     //对12个元素重新赋值
 7     for (int i = 0; i < 12; i++)
 8         (*pt)[i] = i;
 9 
10     auto p = [ptr = move(pt)]{
11         for (int num : *ptr)
12         cout << "current value is:" << num << endl;
13     };
14     p();
15 }
16 
17 int main()
18 {
19    int m = 0;
20    int n = 0;
21    [&, n] (int a) mutable { m = ++n + a; }(4);
22    cout << m << endl << n << endl;
23 }
posted @ 2025-04-01 08:55  左边的翼  阅读(115)  评论(0)    收藏  举报