type - 什麼是C++ 11中的lambda表達式?




lambda expression好處 (6)

什麼是C ++ 11中的lambda表達式? 我什麼時候可以用一個? 在他們推出之前,他們解決了哪一類問題是不可能的?

一些例子和用例會很有用。


什麼是lambda函數?

lambda函數的C ++概念來源於lambda演算和函數式編程。 lambda是一個未命名的函數,對於不可重用且不值得命名的代碼片段很有用(在實際編程中,而非理論上)。

在C ++中,lambda函數是這樣定義的

[]() { } // barebone lambda

或者所有的榮耀

[]() mutable -> T { } // T is the return type, still lacking throw()

[]是捕獲列表, ()參數列表和{}函數體。

捕獲列表

捕獲列表定義了lambda外部應該在函數體內可用的內容以及如何實現。 它可以是:

  1. 值:[x]
  2. 參考文獻[&x]
  3. 目前在參考範圍內的任何變量[&]
  4. 與3相同,但按值[=]

你可以用逗號分隔的列表[x, &y]混合上述任何一個。

參數列表

參數列表與其他C ++函數中的參數列表相同。

功能體

實際調用lambda時將執行的代碼。

返回類型扣除

如果lambda只有一個return語句,則返回類型可以省略,並且具有隱式類型的decltype(return_statement)

易變的

如果一個lambda被標記為mutable(例如[]() mutable { } ),那麼它允許突變已被值捕獲的值。

用例

由ISO標准定義的庫很大程度上受益於lambda表達式,並提高了可用性的幾個方面,因為現在用戶不必在一些可訪問的範圍內使用小函數來混淆他們的代碼。

C ++ 14

在C ++中,14個lambda通過各種提議得到了擴展。

初始化的Lambda捕獲

捕獲列表的一個元素現在可以用=來初始化。 這允許重命名變量並通過移動來捕獲。 從標準中取得一個例子:

int x = 4;
auto y = [&r = x, x = x+1]()->int {
            r += 2;
            return x+2;
         }();  // Updates ::x to 6, and initializes y to 7.

和一個從維基百科顯示如何捕捉std::move

auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};

通用Lambdas

Lambdas現在可以是泛型的(如果T是周圍範圍內的某個類型模板參數, auto在這裡等於T ):

auto lambda = [](auto x, auto y) {return x + y;};

改進的退貨類型扣除

C ++ 14允許為每個函數推導返回類型,並且不會將其限制為形式return expression;函數return expression; 。 這也延伸到lambda。


問題

C ++包含有用的通用函數,如std::for_eachstd::transform ,它們可以非常方便。 不幸的是,它們的使用也很麻煩,特別是如果您想要應用的函數對於特定函數是獨一無二的。

#include <algorithm>
#include <vector>

namespace {
  struct f {
    void operator()(int) {
      // do something
    }
  };
}

void func(std::vector<int>& v) {
  f f;
  std::for_each(v.begin(), v.end(), f);
}

如果你只在某個地方使用f,那麼寫一整堂課只是為了做一件簡單的事,而不是一件事。

在C ++ 03中,你可能會試圖寫下如下內容,以保持函子本地:

void func2(std::vector<int>& v) {
  struct {
    void operator()(int) {
       // do something
    }
  } f;
  std::for_each(v.begin(), v.end(), f);
}

然而這是不允許的, f不能傳遞給C ++ 03中的模板函數。

新的解決方案

C ++ 11引入lambda允許你編寫一個內聯的匿名函數來替換struct f 。 對於小的簡單例子,它可以更清晰地閱讀(它將所有內容保存在一個地方),並且可能更容易維護,例如以最簡單的形式:

void func3(std::vector<int>& v) {
  std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}

Lambda函數只是匿名函數的語法糖。

返回類型

在簡單情況下,lambda的返回類型是為您推導的,例如:

void func4(std::vector<double>& v) {
  std::transform(v.begin(), v.end(), v.begin(),
                 [](double d) { return d < 0.00001 ? 0 : d; }
                 );
}

但是當你開始編寫更複雜的lambda時,你會很快遇到編譯器無法推導出返回類型的情況,例如:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

為了解決這個問題,你可以使用-> T來明確指定一個lambda函數的返回類型:

void func4(std::vector<double>& v) {
    std::transform(v.begin(), v.end(), v.begin(),
        [](double d) -> double {
            if (d < 0.0001) {
                return 0;
            } else {
                return d;
            }
        });
}

“捕捉”變量

到目前為止,我們還沒有使用除傳遞給它內部lambda以外的任何內容,但我們也可以在lambda內使用其他變量。 如果你想訪問其他變量,你可以使用capture子句(表達式的[] ),這個例子中迄今還沒有被使用,例如:

void func5(std::vector<double>& v, const double& epsilon) {
    std::transform(v.begin(), v.end(), v.begin(),
        [epsilon](double d) -> double {
            if (d < epsilon) {
                return 0;
            } else {
                return d;
            }
        });
}

您可以通過引用和值來捕獲,您可以分別使用&=指定值:

  • [&epsilon]通過引用捕獲[&epsilon]
  • [&]通過引用捕獲lambda中使用的所有變量
  • [=]通過值捕獲lambda中使用的所有變量
  • [&, epsilon]用[& [&, epsilon]捕獲變量,但是按值賦值為epsilon
  • [=, &epsilon]用[=]捕獲變量,但通過引用獲得epsilon

生成的operator()在默認情況下是const ,默認情況下,當您訪問它們時,捕獲的含義將是const 。 這樣做的結果是每個具有相同輸入的調用都會產生相同的結果,但是您可以將lambda標記為mutable以請求生成的operator()不是const


lambda函數是您創建的匿名函數。 它可以捕獲一些已經解釋過的變量(例如http://www.stroustrup.com/C++11FAQ.html#lambda ),但有一些限制。 例如,如果有這樣的回調接口,

void apply(void (*f)(int)) {
    f(10);
    f(20);
    f(30);
}

你可以在現場編寫一個函數來使用它,就像下面通過的函數一樣:

int col=0;
void output() {
    apply([](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

但是你不能這樣做:

void output(int n) {
    int col=0;
    apply([&col,n](int data) {
        cout << data << ((++col % 10) ? ' ' : '\n');
    });
}

因為C ++ 11標準的局限性。 如果你想使用捕獲,你必須依靠圖書館和

#include <functional> 

(或其他一些類似於算法的STL庫來間接獲取它),然後使用std :: function而不是像下面這樣的參數傳遞普通函數:

#include <functional>
void apply(std::function<void(int)> f) {
    f(10);
    f(20);
    f(30);
}
void output(int width) {
    int col;
    apply([width,&col](int data) {
        cout << data << ((++col % width) ? ' ' : '\n');
    });
}

lambda expression的最佳解釋之一是由C ++ Bjarne Stroustrup的作者在他的書***The C++ Programming Language***第11章( ISBN-13:978-0321563842 )中給出的:

What is a lambda expression?

一個lambda表達式 ,有時也被稱為lambda函數,或者(嚴格來說不正確,但俗稱)作為lambda表達式 ,是定義和使用匿名函數對象的簡化符號。 我們沒有用operator()定義一個命名類,而是後來創建該類的一個對象,最後調用它,我們可以使用簡寫。

When would I use one?

當我們想將操作作為參數傳遞給算法時,這是特別有用的。 在圖形用戶界面(以及其他地方)的情況下,這樣的操作通常被稱為回調

What class of problem do they solve that wasn't possible prior to their introduction?

在這裡,我想每個用lambda表達式完成的操作都可以在沒有它們的情況下解決,但是代碼更多,複雜度更高。 Lambda表達式這是您的代碼優化方式,也是讓它更具吸引力的一種方式。 Stroustup感到難過:

有效的優化方法

Some examples

通過lambda表達式

void print_modulo(const vector<int>& v, ostream& os, int m) // output v[i] to os if v[i]%m==0
{
    for_each(begin(v),end(v),
        [&os,m](int x) { 
           if (x%m==0) os << x << '\n';
         });
}

或通過功能

class Modulo_print {
         ostream& os; // members to hold the capture list int m;
     public:
         Modulo_print(ostream& s, int mm) :os(s), m(mm) {} 
         void operator()(int x) const
           { 
             if (x%m==0) os << x << '\n'; 
           }
};

甚至

void print_modulo(const vector<int>& v, ostream& os, int m) 
     // output v[i] to os if v[i]%m==0
{
    class Modulo_print {
        ostream& os; // members to hold the capture list
        int m; 
        public:
           Modulo_print (ostream& s, int mm) :os(s), m(mm) {}
           void operator()(int x) const
           { 
               if (x%m==0) os << x << '\n';
           }
     };
     for_each(begin(v),end(v),Modulo_print{os,m}); 
}

如果你需要你可以像下面這樣命名lambda expression

void print_modulo(const vector<int>& v, ostream& os, int m)
    // output v[i] to os if v[i]%m==0
{
      auto Modulo_print = [&os,m] (int x) { if (x%m==0) os << x << '\n'; };
      for_each(begin(v),end(v),Modulo_print);
 }

或者假設另一個簡單樣本

void TestFunctions::simpleLambda() {
    bool sensitive = true;
    std::vector<int> v = std::vector<int>({1,33,3,4,5,6,7});

    sort(v.begin(),v.end(),
         [sensitive](int x, int y) {
             printf("\n%i\n",  x < y);
             return sensitive ? x < y : abs(x) < abs(y);
         });


    printf("sorted");
    for_each(v.begin(), v.end(),
             [](int x) {
                 printf("x - %i;", x);
             }
             );
}

將生成下一個

0

1

0

1

0

1

0

1

0

1

0排序x - 1; x - 3; x - 4; x - 5; x - 6; x - 7; x - 33;

[] - 這是捕獲列表或lambda introducer :如果lambdas不需要訪問他們的本地環境,我們可以使用它。

從書中引用:

lambda表達式的第一個字符總是[ 。 lambda介紹人可以採取各種形式:

[] :空的捕獲列表。 這意味著在lambda體中不能使用周圍環境的本地名稱。 對於這樣的lambda表達式,數據是從參數或非本地變量中獲得的。

[&] :通過引用隱式捕獲。 所有本地名稱都可以使用。 所有本地變量都通過引用來訪問。

[=] :按值隱式捕獲。 所有本地名稱都可以使用。 所有名稱都是指在lambda表達式的調用點處獲取的局部變量的副本。

[捕獲列表]:明確捕獲; 捕獲列表是要通過引用或按值捕獲(即,存儲在對像中)的局部變量的名稱的列表。 名稱前帶有&的變量被引用捕獲。 其他變量是通過值捕獲的。 捕獲列表還可以包含這個和名稱,後面跟著......作為元素。

[&,capture-list] :通過引用隱式地捕獲列表中未提及名稱的所有局部變量。 捕獲列表可以包含這個。 列出的名字不能以&開頭。 捕獲列表中指定的變量是通過值捕獲的。

[=,capture-list] :通過值隱式地捕獲列表中未提及名稱的所有局部變量。 捕獲列表不能包含這個。 列出的名稱必須以“&”開頭。 捕獲列表中命名的變量通過引用被捕獲。

請注意,以&開頭的本地名稱始終由引用捕獲,並且本地名稱不會由&始終由值捕獲。 只有通過引用捕獲才能修改調用環境中的變量。

Additional

Lambda expression格式

其他參考:



那麼,我發現的一個實際用途是減少鍋爐板代碼。 例如:

void process_z_vec(vector<int>& vec)
{
  auto print_2d = [](const vector<int>& board, int bsize)
  {
    for(int i = 0; i<bsize; i++)
    {
      for(int j=0; j<bsize; j++)
      {
        cout << board[bsize*i+j] << " ";
      }
      cout << "\n";
    }
  };
  // Do sth with the vec.
  print_2d(vec,x_size);
  // Do sth else with the vec.
  print_2d(vec,y_size);
  //... 
}

沒有lambda,你可能需要為不同的bsize情況做些事情。 當然你可以創建一個函數,但是如果你想限制靈魂用戶函數範圍內的用法呢? 拉姆達的性質滿足了這個要求,我在這種情況下使用它。





c++-faq