Lambda 表達式簡介

Lambda 表達式

【原文轉自 https://github.com/changkun/modern-cpp-tutorial/blob/master/book/zh-cn/03-runtime.md#31-lambda-%E8%A1%A8%E8%BE%BE%E5%BC%8F

Lambda 表達式是現代 C++ 中最重要的特性之一,而 Lambda 表達式,實際上就是提供了一個類似匿名函數的特性,而匿名函數則是在需要一個函數,但是又不想費力去命名一個函數的情況下去使用的。

這樣的場景其實有很多很多, 所以匿名函數幾乎是現代編程語言的標配。

基礎

Lambda 表達式的基本語法如下:

[捕獲列表](參數列表) mutable(可選) 異常屬性 -> 返回類型 {
// 函數體
}

 

上面的語法規則除了 [捕獲列表] 內的東西外,其他部分都很好理解,只是一般函數的函數名被略去, 返回值使用了一個 -> 的形式進行。

所謂捕獲列表,其實可以理解爲參數的一種類型,Lambda 表達式內部函數體在默認情況下是不能夠使用函數體外部的變量的, 這時候捕獲列表可以起到傳遞外部數據的作用。

根據傳遞的行爲,捕獲列表也分爲以下幾種:

1. 值捕獲

與參數傳值類似,值捕獲的前提是變量可以拷貝,不同之處則在於,被捕獲的變量在 Lambda 表達式被創建時拷貝, 而非調用時才拷貝:

void lambda_value_capture() {
    int value = 1;
    auto copy_value = [value] {
        return value;
    };
    value = 100;
    auto stored_value = copy_value();
    std::cout << "stored_value = " << stored_value << std::endl;
    // 這時, stored_value == 1, 而 value == 100.
    // 因爲 copy_value 在創建時就保存了一份 value 的拷貝
}

2. 引用捕獲

與引用傳參類似,引用捕獲保存的是引用,值會發生變化。

void lambda_reference_capture() {
    int value = 1;
    auto copy_value = [&value] {
        return value;
    };
    value = 100;
    auto stored_value = copy_value();
    std::cout << "stored_value = " << stored_value << std::endl;
    // 這時, stored_value == 100, value == 100.
    // 因爲 copy_value 保存的是引用
}

3. 隱式捕獲

手動書寫捕獲列表有時候是非常複雜的,這種機械性的工作可以交給編譯器來處理,這時候可以在捕獲列表中寫一個 &= 向編譯器聲明採用引用捕獲或者值捕獲.

總結一下,捕獲提供了 Lambda 表達式對外部值進行使用的功能,捕獲列表的最常用的四種形式可以是:

  • [] 空捕獲列表
  • [name1, name2, ...] 捕獲一系列變量
  • [&] 引用捕獲, 讓編譯器自行推導引用列表
  • [=] 值捕獲, 讓編譯器自行推導值捕獲列表

 例如本章前面的值捕獲例子代碼,其中的lambda表達式 auto copy_value = [value] { return value; } 可改成 auto copy_value = [=] { return value; },具體如下代碼所示:

void lambda_value_capture() {
    int value = 1;
    auto copy_value = [=] {
        return value;
    };
    value = 100;
    auto stored_value = copy_value();
    std::cout << "stored_value = " << stored_value << std::endl;
    // 這時, stored_value == 1, 而 value == 100.
    // 因爲 copy_value 在創建時就保存了一份 value 的拷貝
}

 

4. 表達式捕獲

這部分內容需要了解右值引用以及智能指針

上面提到的值捕獲、引用捕獲都是已經在外層作用域聲明的變量,因此這些捕獲方式捕獲的均爲左值,而不能捕獲右值。

C++14 給與了我們方便,允許捕獲的成員用任意的表達式進行初始化,這就允許了右值的捕獲, 被聲明的捕獲變量類型會根據表達式進行判斷,判斷方式與使用 auto 本質上是相同的:

#include <iostream>
#include <memory>  // std::make_unique
#include <utility> // std::move

void lambda_expression_capture() {
    auto important = std::make_unique<int>(1);
    auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
        return x+y+v1+(*v2);
    };
    std::cout << add(3,4) << std::endl;
}

在上面的代碼中,important 是一個獨佔指針,是不能夠被 "=" 值捕獲到,這時候我們可以將其轉移爲右值,在表達式中初始化。

泛型 Lambda

 auto 關鍵字不能夠用在參數表裏,這是因爲這樣的寫法會與模板的功能產生衝突。 但是 Lambda 表達式並不是普通函數,所以在沒有明確指明參數表類型的情況下,Lambda 表達式並不能夠模板化。

幸運的是,這種麻煩只存在於 C++11 中,從 C++14 開始,Lambda 函數的形式參數可以使用 auto 關鍵字來產生意義上的泛型:

auto add = [](auto x, auto y) {
    return x+y;
};

add(1, 2);
add(1.1, 2.2);

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章