C++/匿名函數

< C++

匿名函數,即Lambda函數,可以說是C++11中引入的最重要的特性了。在C++中lambda相當於能自動捕獲變量的函數對象的語法糖。即使簽名相同的不同函數對象(以及匿名函數)的類型不同,不能相互賦值。因此在將lambda賦值給一個變量時,變量類型只能為auto,因為沒法確定lambda的類型。為解決函數對象(以及匿名函數)的類型擦除的複製、傳遞、存儲、調用的問題,C++11引入了std::function

[]{}()可以說是C++中最簡潔的表達式了:

  • []中的內容為捕獲列表
  • ()中的內容為參數列表。當參數列表為空時,()部分可以省略
  • {}為函數體

有時需要函數能訪問和修改一些外部的數據,或者說需要有相應的上下文。函數體與上下文的整體就稱作閉包(closure),在Lambda中是用於捕獲外部變量。在捕獲列表[]中放置函數體中用到的外部變量:

int i = 0, j = 1;
auto f = [i, &j](int a, float b){ ++j; cout << i << ", " << a << ", " << b << endl; };
f(1, 2.0f);

上面這個例子,捕獲了i和j兩個變量,其中i是值捕獲,j是引用捕獲。

當捕獲的變量較多時,C++還提供了一種指定默認捕獲的簡便寫法:

  • [=]指出默認為值捕獲
  • [&]指出默認為引用捕獲。需要注意調用lambda函數時捕獲對象仍在生命周期內。

指定默認捕獲後,其他變量的捕獲方式可寫在捕獲列表後部。下例指定默認為值捕獲,j為引用捕獲:

auto f = [=, &j](int a, float b){ ++j; cout << i << ", " << a << ", " << b << endl; };

為解決捕獲使用C++的移動語義,C++14允許捕獲對象由任意表達式初始化。例如:

std::unique_ptr<int> ptr(new int(10));
auto f = [value = std::move(ptr)] { return *value; };

值引用捕獲的變量默認是const的,如果要在函數體內修改,需要給lambda加上mutable修飾符:

auto f = [=](int a) mutable { cout << i++ << ", " << a << endl; };

值捕獲對象的生命周期與lambda本體的生命周期相同,而引用捕獲則通過捕獲不同的對象,lambda函數實現了閉包的效果,比如下面的代碼就構造了一個斐波那契數列生成器(generator):

如果要明確指出返回類型,可如下例:

auto f = []() -> double { return 5; };