Lambda expression 是之前《C++ 語法再加強:C++0x》一文中列出的 MSVC++10 在 C++ core language 加入的第三項新功能。他的基本概念是一個「匿名函式 (anonymous function)」,可以用來快速地建立一個沒有名稱的 function object 來使用。也因此,除了宣告的方法以及使用目的不太一樣外,實際上他很類似一般的 function。
而 lambda expression 的語法定義(參考 MSDN),則如下:
[]() mutable throw() -> typeid { //function body }
要細分的話,可以把它拆成六個部份,個別的意義分別是:
-
[] : lambda introducer, capture clause
lambda expression 基本上就是由 [] 開始的,而且也是 lambda expression 的語法中絕對不能省略的部分;它的目的是用來告訴編譯器接下來的就是要開始寫 lambda expression 了~
不過實際上除了 introducer 的功能外,他還包含了所謂「capture」的功能,可以把 lambda expression 所在的 scope 內可以讀到的變數抓到 lambda expression 裡使用;而使用上也可以設定為 by value 或 by reference。除了一個一個變數設定外,可以直接使用 = 和 & 設定預設 capture(capture-default);前者是預設將所有變數以 by value 的方式抓進來、後者則是將所有變數以 by reference 的方式抓進來。
下面是用比較單純的例子來說明:
1: [] // 不使用外部的變數 2: [=] // 全部 capture by value 3: [&] // 全部 capture by reference 4: [x, &y] // x by value, y by reference 5: [=, &y] // 除了 y by reference 外,其他全部 by value 6: [&, x] // 除了 x by value 外,其他全部 by reference
其中要注意的是,capture-default(=、&)要放在 capture list 的第一項。
此外,上面的例子裡的 4/5/6,如果在外部變數只有 x 和 y 的情況下,這三種寫法會是等價的。
-
() : parameter declaration list
要傳入這個匿名函釋的變數,基本上就像一般的 function 一樣的用法,不過多加了一些限制:
- 不能有預設值
- 不能有可變長度的參數列表
- 不能有沒命名的參數
另外,在不需要傳參數進 lambda expression 的時候,是可以直接把 () 省略的。
-
mutable : mutable specification
可以省略的東西。加上了 mutable 後,是讓 lambda expression 可以修改 capture by value 的外部變數。
(Heresy 不太瞭的是,要修改的用 by reference 的方法抓近來不就好了?)
-
throw() : exception specification
可以省略的東西。這其實是一般函式就可以加的功能,可以用來指定這個函式會不會丟出例外狀況(exception)、丟出哪種類型的例外狀況;詳細可以參考《C++ Exception Handling》和《Exception Specifications》。
-
-> typeid : return type clause
指定 lambda 回傳值的型別。在 lambda body 沒有回傳(return)或是只有一種回傳路徑的情況下,編譯器會自動判斷回傳的型別,所以可以把這部分也省略掉。
-
{…} : function body
這個 lambda expression 要做的事,就像一般 function 的程式內容。
而 lambda expression 的好處,就是可以不用實際宣告出函式,而直接拿來當 funciton object 使用。實際使用的時候,最普遍的用法應該是用在像是 STL <algorithm> 裡的函式,把 function 當參數傳進去了~
例如下面的程式就是在 C++98 時,要使用 for_each() 時的寫法:
class LambdaFunctor { public: void operator()(int n) const { cout << n << " "; } }; int main() { vector<int> v; for( int i = 0; i < 10; ++i ) v.push_back(i); for_each( v.begin(), v.end(), LambdaFunctor() ); cout << endl; }
而如果改用 lambda expression 的話,就可以變成:
int main() { vector<int> v; for (int i = 0; i < 10; ++i) v.push_back(i); for_each(v.begin(), v.end(), [](int n) { cout << n << " "; } ); cout << endl; }
這樣應該很明顯看得出來,使用 lambda expression 的好處,就是可以不必額外寫出 LambdaFunctor 這個 function class 了~
而如果要使用 capture-list 的話,下方是一個簡單的例子,他會把 vector v 中,值介於 4 到 7 之間的項目給刪除掉:
int main() { vector<int> v; for( int i = 0; i < 10; ++i ) v.push_back(i); int x = 4; int y = 7; v.erase( remove_if( v.begin(), v.end(), [x, y](int n) { return x < n && n < y; } ), v.end()); for_each(v.begin(), v.end(), [](int n) { cout << n << " "; }); cout << endl; }
如果在 lambda introducer 裡的 capture list 裡沒有加上 x、y 的話,是會因為在 lambda function 裡找不到變數 x、y 而編譯錯誤的。
如果是要用 by reference 的話,下面是一個把 vector 加總的簡單範例:
vector<int> v; //... int sum = 0; for_each( v.begin(), v.end(), [&sum]( int n ){ sum += n; } );
最後,由於 lambda expression 會產生類似 function obejct 的物件,所以其實我們可以用 auto(參考)或是 tr1 的 function(參考)來把 lambda expression 儲存下來;不過可能要注意的是,如果是用 auto 來儲存 lambda expression 的話,每次都會是不同的型別的!下面就是一個簡單的範例:
#include <functional> #include <iostream> using namespace std; int main() { function<void (int)> g1 = [](int n){cout << n;}; function<void (int)> g2 = [](int n){cout << n;}; auto g3 = [](int n){cout << n;}; auto g4 = [](int n){cout << n;}; cout << typeid( g1 ).name() << endl; cout << typeid( g2 ).name() << endl; cout << typeid( g3 ).name() << endl; cout << typeid( g4 ).name() << endl; }
這樣的一段程式用 VC10 編譯後執行的結果會是:
class std::tr1::function class std::tr1::function class `anonymous namespace':: class `anonymous namespace'::
其中可以發現,如果用 auto 來儲存 lambda expression 的話,兩個同樣寫法的 lambda expression 會被當成兩種不同型別的物件,所以兩者也是不能互換的~
另外一提,由於 lambda expression 本身可以直接當作 function object 來使用,所以下面的語法是合法的,就相當於直接呼叫這個沒有名稱的 function object:
int a = [](){return 5;}();
甚至,下面這兩行看起來很詭異的程式,也是合法的語法~
[](){}(); []{}();
參考資料:
[…] Visual Studio 2019 升級到 Visual Studio 2022 時,踩到的新版編譯器在 lambda expression […]
讚讚
[…] 所推出的 Lambda expression 這種匿名函式,Heresy […]
讚讚
[…] 在很久以前,Heresy 就曾經曾對 C++11(當時還叫做 C++0x)的新語法、Lambda Experssion 寫過簡單的介紹、《C++0x:Lambda expression》了~ […]
讚讚
[…] pData 後,就去呼叫 GSL 的 finally,並傳一個 lambda function 給他,而這個 lambda function 做的事情,就是去把 pData […]
讚讚
[…] 加入的 lambda expression 在 Heresy 來看,是個很實用的功能,Heresy 目前也常常會用到它;在 C++14 […]
讚讚
[…] 基本上,上面三個函式在做的事情都是一樣的,只是傳入 func 這個 callable object 的方式、型別不同而已。而實際上,要使用的方法,是完全相同的~如果是把 lambda expression(介紹)傳進去的話,程式就是: […]
讚讚
[…] Log 在格式化、以及篩選時所使用 expression…不知道之後有沒有可能直接用 C++ 標準的 lambda expression 來做?Heresy […]
讚讚
[…] Lambda expression 並非 C++11 標準的語法(參考),而是要搭配 Boost.Log 提供的 placeholder 來使用;而如果不習慣這種 Lambda […]
讚讚
3. mutable
原因應該是reference抓進來的有可能是const reference,所以才需要mutable吧!(我猜)
讚Liked by 1 person
這個就不確定了。等哪天研究出來再來分享吧~(茶
讚讚
[…] auto 和 lambda expression 等功能,Heresy […]
讚讚