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表達式寫的回調函數(如:窗口處理函數、線程執行函數、控件消息處理函數等),尤其要注意這個問題
參考