Files
hugo_blog.awin.one/content/posts/2019/2019-12-15_[Cpp 筆記] Lambda/index.md
2025-03-09 23:28:35 +08:00

6.5 KiB
Raw Blame History

slug, title, description, toc, authors, tags, categories, series, date, lastmod, featuredVideo, featuredImage, draft, enableComment
slug title description toc authors tags categories series date lastmod featuredVideo featuredImage draft enableComment
[C++ 筆記] Lambda [C++ 筆記] Lambda true
awin
c++
Programming
C++ 筆記
2019-12-15T00:00:00 2019-12-15T00:00:00 false true

一個簡單的 Lamdba 運算式:

[] (int x, int y) -> bool {
    return x < y;
}
  • 以中括號開頭,中括號被稱為lamdba 導入器lamdba introducer
  • 小括號裡面是lamdba 參數列表lambda parameter list
  • 如果沒有參數,小括號可以省略,[] () {...} 可以簡寫成 [] {...}
  • 箭號(->)後面是回傳的型別,如果沒寫就由 return 自動推斷

將 Lamdba 運算式指定給變數:

auto comapre = [] (int x, int y) -> bool {
    return x < y;
};

Lamdba的擷取子句

以中括號開頭的 lamdba 導入器 可以將外部的變數傳給 Lamdba 運算式正式名稱是「擷取子句capture clause」。
[=] 表示它們會以值擷取captured by value。Scope內的變數可以在 lamdba 內使用,但是不可以改變。
[&] 表示它們會以參考擷取captured by reference。Scope內的變數可以在 lamdba 內使用,可以改變。

以值擷取captured by value

假設有一段程式如下:

void testLambda() {
    float notUsed = 1.0f;
    std::vector<int32_t> numlist{10, 20, 30, 50, 60};
    auto findInRange = [=](int32_t start, int32_t end) {
        for (auto num : numlist) {
            if (num >= start && num <= end) return true;
        }
        return false;
    };

    std::cout << "Result: " << findInRange(25, 35) << "\n";
}

[=]可以用來擷取 lamdba scope 範圍所及的變數,沒有在 Lamdba 運算式裡面被用到的變數就不會被擷取,例如 float notUsed = 1.0f;
另一個重點是:被擷取的變數是不可以更改的。例如,不能在 lambda 裡面這樣寫:

auto findInRange = [=](int32_t start, int32_t end) {
    numlist.push_back(5); // ERROR!
    
    for (auto num : numlist) {
        if (num >= start && num <= end) return true;
    }
    return false;
};

如果一定要在 lambda 內改變擷取的變數,那必須指名 lambda 為 mutable

auto findInRange = [=](int32_t start, int32_t end) mutable { // <-- assign mutable
    numlist.push_back(5);
    
    for (auto num : numlist) {
        if (num >= start && num <= end) return true;
    }
    return false;
};

根據書上解釋 ,可以裡解為 compiler 會將 lamdba 編為一個 class像是

class __Lambda8C1A5 {
public:
    __Lambda8C1A5(const std::vector<int32_t>& arg1) : numlist(arg1) {}
    auto operator()(int32_t start, int32_t end) const {  // const!
        for (auto num : numlist) {
            if (num >= start && num <= end) return true;
        }
        return false;
    }

private:
    std::vector<int32_t> numlist;
};

這也解釋了 lamdba 的擷取範圍與原理。而 mutable 則是讓 operator() 不為 const,如下:

auto findInRange = [=](int32_t start, int32_t end) mutable { // <-- assign mutable
    numlist.push_back(5);
    
    for (auto num : numlist) {
        if (num >= start && num <= end) return true;
    }
    return false;
};

...

class __Lambda8C1A5 {
public:
    __Lambda8C1A5(const std::vector<int32_t>& arg1) : numlist(arg1) {}
    auto operator()(int32_t start, int32_t end) {  // No const here
        for (auto num : numlist) {
            if (num >= start && num <= end) return true;
        }
        return false;
    }

private:
    std::vector<int32_t> numlist;
};

以值擷取特定的變數

若只需要擷取特定的變數,那就直接在 lamdba 導入器(就是[])寫入變數名稱,例如:

int var1 = 10;
int var2 = 20;
int var3 = 30;

auto afunc = [var1, var2] () {
    ...
};

以參考擷取captured by reference

[&] 會擷取 scope 內的所有外部變數,而且可以修改:

void testLambda() {
    float notUsed = 1.0f;
    std::vector<int32_t> numlist{ 10, 20, 30, 50, 60 };
    auto findInRange = [&](int32_t start, int32_t end) {    // Use & here
        numlist.push_back(100); // OK

        for (auto num : numlist) {
            if (num >= start && num <= end) return true;
        }
        return false;
    };

    std::cout << "Result: " << findInRange(25, 35) << "\n";
    std::cout << "numlist: ";
    for (auto n : numlist) {
        std::cout << n << " ";
    }
    std::cout << "\n"; // Output numlist: 10 20 30 50 60 100
}

以參考擷取特定的變數

但是直接參考全部的外部變數不是好的作法,這讓你有機會做出一些意外的修改,所以請擷取有需要的變數就好:

void testLambda() {
    float notUsed = 1.0f;
    std::vector<int32_t> numlist{ 10, 20, 30, 50, 60 };
    
    auto findInRange = [&numlist](int32_t start, int32_t end) {
        numlist.push_back(100); // OK

        for (auto num : numlist) {
            if (num >= start && num <= end) return true;
        }
        return false;
    };

    ...
}

如果有多個變數需要擷取,那就用 , 分開:

auto findInRange = [&numlist, &var1, &var2](int32_t start, int32_t end) {
    ...
};

混合擷取

以值擷取跟參考擷取也可以寫在一起:

auto findInRange = [=, &numlist](int32_t start, int32_t end) {
    ...
};

上面的例子中,numlist 會是參考擷取,其他的外部變數則是以值擷取。

或是:

auto findInRange = [&, numlist](int32_t start, int32_t end) {
    ...
};

上面的例子中,numlist 會以值擷取,其他的外部變數則是參考擷取。

但是,如果已經使用了 = ,就不可以再以值擷取其他變數,像是 [=, numlist] 就是不合法的。
反之,如果已經使用了 &,就不可以再參考擷取其他變數,像是 [&, &var1] 就是不合法的。

存取 class

Lamdba 寫在 class 裡面的時候,不論「以值擷取 」或是「以參考擷取」都沒辦法傳遞成員變數member variable只能傳遞 this,透過 this 來存取成員變數。例:

class BigBuffer {
public:
    void modify(int x, int y, ...) {
        auto modifyBuffer = [this] () {  // Use this
            if (buffer) {  // equal to this->buffer
                // do something with buffer
            }
        };
        ...
    }

private:
    uint32_t bufferSize = 0;
    std::unique_ptr<uint8_t[]> buffer = nullptr;
};