C++11 lambda表達式與仿函數


對lambda表達式用法進行總結,

參考:1. https://docs.microsoft.com/en-us/cpp/cpp/lambda-expression-syntax?view=vs-2019

      2.《深入理解C++11》

  lambda函數在C++11標准中默認是內聯的,類似於其他語言中的局部函數(local function),或內嵌函數(nested function)。

lambda

   lambda表達式與普通函數最大的區別之一是:lambda函數可以通過捕捉列表訪問一些上下文中的數據,在捕捉列表中描述了哪些數據可以被lambda使用。

  在C風格編程中,使用函數指針可以將一個函數可以傳遞給另外一個函數。函數指針不便於維護和理解。傳遞進來的函數可能被定義代碼的其他位置。並且函數指針不是類型安全的。現代C++提供重載了()操作符的函數對象(function objects)/類,這就使得它們可以通過類似函數的形式被調用。創建函數對象最方便的方式是labmda表達式。

 

       上述lambda表達式可以理解為:具有一個int類型的參數,且返回類型為bool,該函數用於判斷變量的值是否大於x且小於y。注意:x y是定義在上下文中的,可以在lambda表達式中獲取。[=]表明x y的值是通過值的形式捕獲的(captured by value),也就是說在lambda表達式中x y是這些值的拷貝。

  In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it is invoked or passed as an argument to a function. Typically lambdas are used to encapsulate a few lines of code that are passed to algorithms or asynchronous methods.lambda是一種在函數被調用處/傳遞變量給函數時定義匿名函數對象(閉包)的便利方式。典型地,lambda用於包裹少量行數的代碼,用於傳遞給算法或匿名函數。

 

capture list

       在labmda表達式的[]中,用於捕獲外部變量,並指明是通過引用,還是通過值的形式捕獲。在變量前如果有&符號則表明是通過引用捕獲的;如果沒有&則表明通過值捕獲的(拷貝一份)。

注意:

       在捕獲列表中,= & this都最多只能出現一次。

 

 

  • lambda表達式內部可以訪問表達式外部的變量,capture list中的變量是在lambda表達式之前已經定義的變量,稱為“捕獲”了外部變量。

 

 

 

 

 

 

 

 

  • [] 不捕獲任何變量。
  • [&] 捕獲外部作用域中所有變量,並作為引用在函數體中使用(按引用捕獲)。
  • [=] 捕獲外部作用域中所有變量,並作為副本copy在函數體中使用(按值捕獲)。
  • [=&foo] 按值捕獲外部作用域中所有變量,並按引用捕獲 foo 變量。
  • [bar]按值捕獲 bar 變量,同時不捕獲其他變量。
  • [this] 捕獲當前類中的 this 指針,讓 lambda 表達式擁有和當前類成員函數同樣的訪問權限。如果已經使用了 & 或者 =,就默認添加此選項。捕獲 this 的目的是可以在 lamda 中使用當前類的成員函數和成員變量。

  注意,捕獲列表有:引用,值兩種方式,在變量值修改、多線程等情形下需要注意。

對於值傳遞,外部變量值改變不影響lamda內部的值,但是對於引用傳遞,外部變量值改變會影響lambda內部,調用labmda時需要注意。

 

如何捕獲指針?:

 

使用捕獲列表時需注意:

  • 引用捕獲可被用於修改外部變量,但是值捕獲則不會,(mutable允許你將拷貝的副本進行修改,但是不會影響到外部的變量)
  • 引用捕獲會影響外部變量的更新,但是值捕獲不會
  • 引用捕獲會引入生命周期依賴(lifetime dependency),但是值捕獲則不會。這在lambda異步運行時尤為重要。如果你在異步運行的lambda表達式中通過引用捕獲了一個外部變量,該變量很有可能在lambda未執行完就被銷毀了,這就會導致訪問沖突(access violation)。

Generalized capture(C++14)

  在C++14中,你可以在lambda捕獲列表中引入並初始化新變量,而無需在lambda函數的封閉范圍{}內存在這些變量。變量的初始化可以使用任何表達式;新變量的類型由初始化表達式自動推導。

該特性的一個好處是,你可以從外部環境中捕獲move-only變量(例如:unique_ptr),並且在lambda表達式中使用它。

 

 

parameter list參數列表

       除捕獲列表外,lambda表達式還可以輸入參數。參數列表是可選的,且在多數情況下類似於函數列表。

 

       在C++14中,如果參數是generic(泛型?),你可以使用auto關鍵字作為類型說明符。auto告訴編譯器將函數調用運算符創建為模板(This tells the compiler to create the function call operator as a template)。而且labmda表達式可以以另外一個labmda表達式作為參數。

mutable specification

       通過值傳遞進labmda表達式的變量是const-by-value,即在labmda內部不能修改這些變量,但是使用mutable修飾后,表達式內部就可以修改了。

       exception指示符:表明lambda表達式是否會拋出異常,例如使用noexcept就表示該表達式不會拋出異常。

  典型情況下,lambda的函數調用操作符(lambda’s function call operator)是const-by-value,但是通過使用mutable關鍵字可以解除該限定。該關鍵字並不會產生mutable數據成員。使用mutable關鍵字使得labmda表達式函數體可以修改捕獲的外部變量的值。

Typically, a lambda's function call operator is const-by-value, but use of the mutable keyword cancels this out. It does not produce mutable data members. The mutable specification enables the body of a lambda expression to modify variables that are captured by value.

  在C++11中,默認情況下lambda函數是一個const函數,按照規則,一個const的成員函數是不能在函數體中改變非靜態成員變量的值得。如果在lambda表達式中有mutable,則不論是通過引用還是參數傳遞的方式捕獲的外部變量,在lambda表達式內部都可以修改,且編譯器不會報錯。示例:

 

 

exception specification

       你可以使用noexcept異常說明符來表明lambda表達式不會拋出任何異常。

返回類型

  lambda表達式的返回值是自動推斷的。你不必使用auto關鍵字,除非你指定了一個trailing-return-type。trailing-return-type指的是普通函數或方法的返回部分。在lambda表達式中,返回類型必須在參數列表后,且需要在返回類型前添加 -> trailing-return-type關鍵字。

       如果lambda表達式只有一個return語句,或該表達式根本不返回值,你可以省略lambda表達式的返回部分。如果lambda函數體只有一個返回語句,編譯器會根據返回表達式進行推斷該表達式的返回類型。在其他情況下,編譯器將返回類型推斷為void。

  當表達式中只有一個返回語句,或根本沒有返回語句時,可以省略labmda表達式的返回類型。You can omit(忽略省略) the return-type part of a lambda expression if the lambda body contains just one return statement or the expression does not return a value.

返回類型格式:

       -> type

例如:

 

lambda body

lambda表達式與普通函數體可包含的內容相同,可以使用如下變量:

  • l  通過[]捕獲的變量
  • l  參數列表
  • l  局部聲明的變量
  • l  如果捕獲了this后,this的數據成員等
  • l  靜態變量,例如:全局變量

 

lambda使用

l  直接調用lambda

 

l  將lambda表達式作為變量傳遞給其他函數

 

lambda表達式嵌套

在lambda表達式中可以嵌套另外一個lambda表達式

函數與lambda表達式對比

       當你編寫代碼時,你可能會使用函數指針和函數對象來解決問題和進行計算,特別是使用C++標准庫算法(C++ Standard Library algorithms)時。函數指針和函數對象有其優點但是也有缺點:函數指針擁有最小的語法開銷(syntactic overhead)但是在其范圍內不能保留狀態;函數對象可以維護狀態但是需要在類中定義函數的語法開銷。

       lambda表達式結合了函數指針,函數對象的優點,並避免了它們的缺點。與函數對象類似,lambda比較靈活且可以維護狀態(maintain state);但是與函數對象不同的是,它的緊湊語法不需要顯式的類定義(explicit class definition)。

 仿函數

  在C++11之前,在使用STL算法時,通常會用到一種特殊的對象,稱之為函數對象,或仿函數(functor)。仿函數簡單地說,就是重定義了成員函數operator()的一種自定義類型對象。這樣的對象在代碼使用時與函數一樣,但是其本質卻並非函數。

class _functor
{
public:
    int operator()(int x, int y)
    {
        return x + y;
    }
};

int main(int argc, char *argv[])
{
    _functor totalChild;
    return totalChild(5, 6);
}

  相對於函數,仿函數可以擁有初始狀態,一般通過class定義私有成員,並在聲明對象的時候進行初始化。私有成員的狀態就成了仿函數的初始狀態。而由於聲明一個仿函數對象可以擁有多個不同初始狀態的實例,因此可以借仿函數產生多個功能類似卻不同的仿函數實例。例如:

class Tax
{
private:
    float rate;
    int base;
public:
    Tax(float r, int b)
        :rate(r),
        base(b)
    {}
    float operator()(float money){ return (money - base)*rate; }
};

int main(int argc, char *argv[])
{
    //===functor
    Tax high(0.40, 30000);
    Tax middle(0.25, 20000);

    cout << "tax over 3w: " << high(37500) << endl;
    cout << "tax over 2w: " << middle(27500) << endl;
}

  可以看出,帶狀態的仿函數與lambda類似,除去語法層面上的不同,lambda和仿函數有着相同的內涵-都可以捕捉一些變量作為初始狀態,並接受參數進行運算。而事實上,仿函數是編譯器實現lambda的一種方式,在實現階段,通常編譯器都會把lambda函數轉化為一個仿函數對象。因此,在C++11中,lambda可以視為仿函數的一種等價形式了,或者是“語法糖”。

  lambda書寫簡單,而且可以直接在STL算法中使用,可以部分替代仿函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM