C++ lambda表達式


lambda表達式又稱匿名函數Anonymous function),其構造了一個可以在其作用范圍內捕獲變量的函數對象。

lambda表達式實際為一個仿函數functor,編譯器后會生成一個匿名類(注:這個類重載了()運算符)

與普通函數指針相比,Lambda表達式可以包含數據成員,也就是說它是可以有狀態的。下面舉一個簡單的例子說明一下:

int sum = 0;
std::vector<int> arry = { 1,2,3,4,5 };
std::for_each(begin(arry), end(arry), [&sum](int x){sum += x;});

上述代碼被編譯器展開后,會變成:

struct lambda_b53d8cae67476f0e5f04d9defa3a2e2b
{
private:
    int* m_pSum;
public:
    lambda_b53d8cae67476f0e5f04d9defa3a2e2b(int* pSum)
    {
        m_pSum = pSum;
    }

    void operator()(int x) const 
    {
        *m_pSum += x;
    }
};


int sum = 0;
std::vector<int> arry = { 1,2,3,4,5 };
lambda_b53d8cae67476f0e5f04d9defa3a2e2b obj(&sum);
std::for_each(begin(arry), end(arry), obj);

 

lambda表示式的結構

從C++11起,開始提供了匿名函數的支持,一個lambda表達式形如:

[capture] (parameters) specifiers -> return_type { body }

capture

捕獲的外部變量列表,通過逗號分隔,可進行傳值捕獲或者引用捕獲,lambda表達式與這些捕獲的外部變量會構成一個閉包Closure),外部變量為閉包的成員變量

int g_Value = 0;
class CLambda
{
protected:
    int m_Value;
public:
    void Test1(int InValue)
    {
        int Value = 0;
        auto a1 = [](int x) {/*僅能訪問全局外部變量*/};
        auto a2 = [Value](int x) {/*值傳遞局部變量Value*/};
        auto a3 = [this](int x) {/*值傳遞this指針*/};
        auto a4 = [&Value](int x) {/*引用傳遞局部變量Value*/};
        auto a5 = [=](int x) {/*值傳遞所有可訪問的外部變量*/};
        auto a6 = [&](int x) {/*引用傳遞所有可訪問的外部變量*/};
        auto a7 = [=, &Value](int x) {/*引用傳遞局部變量Value,值傳遞所有其他可訪問的外部變量*/};
        auto a8 = [&, Value](int x) {/*值傳遞局部變量Value,引用傳遞所有其他可訪問的外部變量*/};
    }
};

① 全局變量、靜態變量不用顯示寫在外部變量列表中,可直接在lambda表達式中讀寫

② 傳值捕獲的外部變量是一個副本,默認情況下在lambda表達式中是只讀的,若想修改該副本,需要在lambda表達式上添加mutable關鍵字  如:auto a2 = [Value](int x) mutable {Value++;};

---------------------------------------------------------------------------

在C++中,mutable是為了突破const的限制而設置的,用來修飾類的非靜態、非常量的數據成員,讓被修飾的變量永遠處於可變的狀態

mutable的作用有兩點:
a. 保持常量對象中大部分數據成員仍然是“只讀”的情況下,實現對個別數據成員的修改;
b. 使類的const函數可以修改其mutable數據成員。

---------------------------------------------------------------------------

③ 在類的非靜態成員函數中定義的lambda表達式可以通過捕捉this指針,來訪問對象的成員變量和成員函數

④ c++14中增加廣義捕獲(Generalized capture):即在捕獲子句中增加並初始化新的變量,該變量不需要在lambda表達式所處的閉包域中存在;

即使在閉包域中存在也會被新變量覆蓋。新變量類型由它的初始化表達式推導得到。一個用途是可以從閉包域中捕獲只供移動的變量並使用它。

int Value = 0;
auto a1 = [Value = 100] { return Value; };// 捕獲子句中定義的Value變量會覆蓋同名的外部局部變量
int result = a1(); // result=100

std::vector<int> Vout = { 10, 20, 30, 40, 50 };
auto a2 = [Vin = std::move(Vout)]{ // 將外部局部變量Vout移動到Vin變量,避免發生拷貝耗時操作
    int sum = 0;
    for (auto n : Vin)
    {
        sum += n;
    }

    return sum;
};

int result2 = a2(); // result2=150

 

parameters

lambda表達式自己的參數列表

1. 若lambda函數沒有形參且沒有被mutable等修飾,則參數的空圓括號可以省略

如:auto a = []{ ++g_Value; };

2. c++14支持auto類型的形參

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

//相當於定義了模板類型()運算符的函數對象
struct unnamed_lambda                            
{
  template<typename T, typename U> 
auto operator()(T x, U y) const {return x + y;} } a;

3. c++14支持可變參數模板

int foo(int n1, int n2, bool flag)
{
    return flag ? n1 : n2;
}

auto a = [](auto&&... params) //可變參數的模板
{
    return (foo(std::forward<decltype(params)>(params)...));
};
int result = a(1, 2, true); //result=1

4. 與普通函數相比,lambda表達式的參數有如下限制

① 參數不能有缺省值   如:int Add1(int a, int b=10) 

② 不能有可變長參數列表  如:int  Add2(int count, ...)

③ 不能有無名參數  如:int Add3(int a, int b, int// 第三個參數為無名參數,用於以后擴展

 

specifiers

說明符,可選。如上文中的mutable

 

return_type

返回類型

在以下情況下,可省略return_type

① 返回值為void類型  如:auto a = [](int x) {x = 100;};  //返回值類型為void

② 所有返回語句用decltype都能檢測到同一類型  如:auto a = [](int x, float y) { if (x > 0) return x + y;  return y; };  //返回值類型推導為float

 

使用lambda表達式

lambda表達式實際為一個函數對象,在使用時要注意lambda表達式以及被其捕獲的外部變量的生命周期

把匿名函數存儲在變量,當做有名函數來使用

lambda表達式的數據類型是函數對象,可用auto類型、std::function模板類型(需#include <functional>)進行保存和調用

當捕獲外部變量列表為空時,也可用普通函數指針進行保存和調用

int Value = 0;

auto a1 = [&Value](int x) {Value = x;};
std::function<float(int,float)> a2 = [Value](int x, float y) { return x + y; }; //需要#include <functional>
a1(100);
a2(100, 200.0f);

// lambda函數的捕獲外部變量列表為空時,可使用普通函數指針來保存
int(*func1_ptr)(int) = [](int x) {return x; };
bool(*func2_ptr)(int, int) = [](int x, int y) { return x > y; };
int n = func1_ptr(1);
bool b = func2_ptr(1, 2);

 

懸掛引用問題

lambda表達式的閉包含有局部變量的引用(懸掛引用  Dangling references),在超出創建它的作用域之外的地方被使用的話,將引發內存越界訪問

std::function<int()> a;
{
    int Value = 0;
    a = [&Value] {return Value; };
}
        
a(); // 局部變量Value超過作用域,已被回收,調用lambda表達式將產生內存越界訪問

對於使用lambda表達式寫的回調函數(如:窗口處理函數、線程執行函數、控件消息處理函數等),尤其要注意這個問題

 

參考

C++ 中的 LAMBDA 表達式

Lambda 表達式 (C++11 起)

 


免責聲明!

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



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