在很久以前,Heresy 就曾經曾對 C++11(當時還叫做 C++0x)的新語法、Lambda Experssion 寫過簡單的介紹、《C++0x:Lambda expression》了~
由於 lambda 這種匿名函式再搭配需要使用 function object 的時候,會是一個相當方便的東西,所以 Heresy 也常常在使用。
而在 C++11 引進後,其實在後面的 C++14 和 C++17 中,也都有針對 lambda 再做一些改進;雖然之前其實也有簡單提過,不過這邊就在整理一下,這兩次改版中,Heresy 個人覺得比較用的到的變化吧~
<!–more–>
Capture clause
在變數的 capture 的部分,最初的 lambda 基本上就是 capture by value 和 capture by reference 兩種形式,在使用上也有不少限制。
在 C++14 的時候,則是加入了「captures with an initialiser」的功能,算是大幅地增加了使用上的彈性。
下面就是一個簡單的範例:
int main() { int x = 10; int y = 11; auto foo = [z = x + y]() { std::cout << z << 'n'; }; foo(); }
從 C++14 開始,在宣告 lambda 的時候,可以同時在 capture clause 裡面,去進行變數的初始化;像在上面的例子裡面,就是會產生一個新的變數 z,他的值等於 x + y。
透過這個機制,在捕捉變數、以及後續的使用,都會相對簡單。
像是下面的例子,就是建立一個變數的參考 x,讓他去指到 c.x,然後在內部修改他的值。
#include <iostream> class CTest { public: int x; }; int main(int argc, char** argv) { CTest c; c.x = 10; auto f = [&x = c.x](){ x = 12; std::cout << x << std::endl; }; f(); std::cout << c.x << std::endl; }
透過這樣的寫法,就可以不用把整個 c capture 進到 lambda 的 scope 內,讓 lambda 內的使用更為簡單。
而到了 C++17,則是又針對 this 的 capture 做了強化,讓他可以直接寫成 capture *this。
之前雖然也可以透過 [this](){} 來存取類別中的成員,但是這樣的 capture 方式,實際上是透過指標來做的,有的時候會碰到超出 scope 的問題、或是指標指到的變數已經被釋放的狀況。
下面就是一個例子:
#include <iostream> struct Baz { auto foo() { return[this]{ std::cout << s << std::endl; }; } std::string s; }; int main() { auto f1 = Baz{ "ala" }.foo(); auto f2 = Baz{ "ula" }.foo(); f1(); f2(); }
在這個例子裡面, f1 和 f2 這兩個 lambda 都是透過臨時變數(Baz)產生的;而他們做的事,就是要把 Baz.s 輸出出來。
但是在實際呼叫 f1() 和 f2() 的時候,lambda 所 capture 到的 this 所指到的臨時物件已經不存在了,所以輸出的結過就會變成有問題了。
而 C++17 新提供的寫法則是 [*this](){},他會把本身複製一份,來避免可能的問題。
在上面的例子中,如果把 Baz::foo() 的寫法改成:
auto foo() { return[*this]{ std::cout << s << std::endl; }; }
就可以避免這個問題了~
但是當然,這邊也要注意的就是,這樣的 cpature 是以複製的形式來進行的,也不見得在每個情境都適用。
另外,可能要稍微注意的是,在現行的標準中,[=] 同時包含了 this;而這個設計會在 C++20 被拿掉,以避免相關的可能錯誤。
Generic lambda
「Generic lambda」也是 C++14 新提供的語法,他的用法有點類似 template function,允許使用者將不同型態的變數、傳遞給 lambda。
下面就是一個範例:
auto foo = [](auto x) { std::cout << x << 'n'; }; foo(10); foo(10.1234); foo("hello world");
可以看到,這邊是傳入的參數 x 的型別是寫成 auto,讓系統自動判斷。
透過這樣的語法,同一個 lambda 就可以對應到不同型別的變數來使用了~
而它的功用,其實再搭配 std::variant 的時候,其實就算是相當顯著了。
當然,其他還有一些改進,像是 C++17 就有支援 constexpr lambda,不過這邊就先不提了。
之後的 C++20 也還會持續改進,也就等到時候再說吧~
參考: