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

238 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
slug: "[C++ 筆記] Lambda"
title: "[C++ 筆記] Lambda"
description:
toc: true
authors:
- awin
tags:
- c++
categories:
- Programming
series:
- C++ 筆記
date: 2019-12-15T00:00:00
lastmod: 2019-12-15T00:00:00
featuredVideo:
featuredImage:
draft: false
enableComment: true
---
一個簡單的 Lamdba 運算式:
```cpp
[] (int x, int y) -> bool {
return x < y;
}
```
<!--more-->
- 以中括號開頭,中括號被稱為*lamdba 導入器lamdba introducer*
- 小括號裡面是*lamdba 參數列表lambda parameter list*
- 如果沒有參數,小括號可以省略,`[] () {...}` 可以簡寫成 `[] {...}`
- 箭號(`->`)後面是回傳的型別,如果沒寫就由 `return` 自動推斷
將 Lamdba 運算式指定給變數:
```cpp
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
假設有一段程式如下:
```cpp
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 裡面這樣寫:
```cpp
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`
```cpp
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像是
```cpp
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`,如下:
```cpp
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 導入器(就是`[]`)寫入變數名稱,例如:
```cpp
int var1 = 10;
int var2 = 20;
int var3 = 30;
auto afunc = [var1, var2] () {
...
};
```
## 以參考擷取captured by reference
`[&]` 會擷取 scope 內的所有外部變數,而且可以修改:
```cpp
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
}
```
## 以參考擷取特定的變數
但是直接參考全部的外部變數不是好的作法,這讓你有機會做出一些意外的修改,所以請擷取有需要的變數就好:
```cpp
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;
};
...
}
```
如果有多個變數需要擷取,那就用 `,` 分開:
```cpp
auto findInRange = [&numlist, &var1, &var2](int32_t start, int32_t end) {
...
};
```
## 混合擷取
以值擷取跟參考擷取也可以寫在一起:
```cpp
auto findInRange = [=, &numlist](int32_t start, int32_t end) {
...
};
```
上面的例子中,`numlist` 會是參考擷取,其他的外部變數則是以值擷取。
或是:
```cpp
auto findInRange = [&, numlist](int32_t start, int32_t end) {
...
};
```
上面的例子中,`numlist` 會以值擷取,其他的外部變數則是參考擷取。
但是,如果已經使用了 `=` ,就不可以再以值擷取其他變數,像是 `[=, numlist]` 就是不合法的。
反之,如果已經使用了 `&`,就不可以再參考擷取其他變數,像是 `[&, &var1]` 就是不合法的。
## 存取 class
Lamdba 寫在 class 裡面的時候,不論[「以值擷取 」](#以值擷取captured-by-value)或是[「以參考擷取」](#以參考擷取captured-by-reference)都沒辦法傳遞成員變數member variable只能傳遞 `this`,透過 `this` 來存取成員變數。例:
```cpp
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;
};
```