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<void __cdecl(int)>class std::tr1::function<void __cdecl(int)>class `anonymous namespace'::<lambda2>class `anonymous namespace'::<lambda3>
其中可以發現,如果用 auto 來儲存 lambda expression 的話,兩個同樣寫法的 lambda expression 會被當成兩種不同型別的物件,所以兩者也是不能互換的∼
另外一提,由於 lambda expression 本身可以直接當作 function object 來使用,所以下面的語法是合法的,就相當於直接呼叫這個沒有名稱的 function object:
int a = [](){return 5;}();
甚至,下面這兩行看起來很詭異的程式,也是合法的語法∼
[](){}();[]{}();
參考資料: