C++14 與 C++17 Lambda Expression 的改變


在很久以前,Heresy 就曾經曾對 C++11(當時還叫做 C++0x)的新語法、Lambda Experssion 寫過簡單的介紹、《C++0x:Lambda expression》了~

由於 lambda 這種匿名函式再搭配需要使用 function object 的時候,會是一個相當方便的東西,所以 Heresy 也常常在使用。

而在 C++11 引進後,其實在後面的 C++14C++17 中,也都有針對 lambda 再做一些改進;雖然之前其實也有簡單提過,不過這邊就在整理一下,這兩次改版中,Heresy 個人覺得比較用的到的變化吧~


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();
}

在這個例子裡面, f1f2 這兩個 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 也還會持續改進,也就等到時候再說吧~


參考:

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.