前景
在coding過(guò)程中對(duì)于共用的代碼塊進(jìn)行封裝以達(dá)成復(fù)用已成為我們的習(xí)慣。不過(guò)復(fù)用的過(guò)程情況多變,對(duì)于某一個(gè)多次使用的代碼塊來(lái)說(shuō),當(dāng)我們知道在本文件外甚至本函數(shù)外基本不會(huì)調(diào)用時(shí),往往并不希望為這個(gè)代碼塊麻煩地做單獨(dú)聲明“污染環(huán)境”,此時(shí)我們可以采用lambda表達(dá)式完成復(fù)用目的。
此外,在使用cpp某些特性時(shí),使用lambda表達(dá)式顯然更加方便 比如作為deleter參數(shù)傳入智能指針時(shí)。
前提
編譯器需要支持c++11及以上。
什么是lambda表達(dá)式
lambda表達(dá)式是定義匿名函數(shù)對(duì)象的便捷方法,通常,lambda用于封裝傳遞到函數(shù)的幾行代碼.
lambda表達(dá)式的組成
[=]/*1*/ ()/*2*/ mutable/*3*/ throw()/*4*/ -> int/*5*/ {}/*6*/
- capture子句
- 參數(shù)列表(optional)
- 可變規(guī)范(optional)
- 異常定義(optional)
- 返回類型(optional)
- 函數(shù)體
capture子句
首先我們可以認(rèn)為lambda表達(dá)式定義了一個(gè)函數(shù)變量,在其語(yǔ)法語(yǔ)義上作為變量處理.
capture子句被lambda用于訪問(wèn)所在作用域的變量, 并指定訪問(wèn)這些變量時(shí)是通過(guò)值拷貝還是引用訪問(wèn).
**ps, 在c++14及以上的規(guī)范中,capture子句還可以用于定義新變量,本文以c++11標(biāo)準(zhǔn)為重點(diǎn), 關(guān)于此細(xì)節(jié)感興趣可以上網(wǎng)查閱)
捕獲類型
在[]中使用&時(shí)代表引用捕獲(ex. [&factor]),此時(shí)lambda對(duì)于捕獲到的變量修改在lambda外部同樣生效,當(dāng)不使用&而直接使用變量名時(shí)代表按值捕獲(ex.[factor]),此時(shí)對(duì)該變量的修改盡在lambda函數(shù)體中生效.
tips:
- [&]表示捕獲的所有變量都是按照引用捕獲的,lambda將會(huì)捕獲所有在函數(shù)體中出現(xiàn)的外部變量.
- [=]表示捕獲的所有變量都是按照值捕獲的
- 可以在capture子句中指定多種捕獲模式,例如
[&total, factor]//值捕獲factor,引用捕獲total
[factor, &total]//值捕獲factor,引用捕獲total
[&, factor]//值捕獲factor,其他變量引用捕獲
[=, &factor]//引用捕獲factor,其他變量值捕獲
- []表示不訪問(wèn)外部變量
- 當(dāng)設(shè)定了捕獲方式默認(rèn)值時(shí),不能再單獨(dú)為其他變量設(shè)置相同的捕獲方式,此外,不能重復(fù)使用標(biāo)識(shí)符.
[=, factor] //error
[&, &factor] //error
[i,i]//error
[&i, i]//error
當(dāng)需要訪問(wèn)類成員時(shí),對(duì)于this的捕獲比較特殊,當(dāng)使用&方式時(shí),捕獲方式是&(this),即copy了this指針?biāo)赶虻牡刂?當(dāng)使用=捕獲時(shí),捕獲方式仍然是&(this),當(dāng)想要引用this所指向的對(duì)象而非其對(duì)象地址時(shí)應(yīng)使用*this(c++17引入),這個(gè)東西非常惡心,c++隨著14 17 20標(biāo)準(zhǔn)的更新改來(lái)改去,推薦直接使用&方式捕獲this.詳情可參考傳送門.另外lambda捕獲this存在陷阱,詳見(jiàn)后文.
參數(shù)列表
lambda表達(dá)式實(shí)質(zhì)上定義了一個(gè)函數(shù)對(duì)象,所以自然包含參數(shù)列表,參數(shù)列表的使用符合函數(shù)標(biāo)準(zhǔn)規(guī)范,我們按照普通函數(shù)語(yǔ)法編寫即可.
可變規(guī)范
lambda表達(dá)式默認(rèn)其通過(guò)值捕獲的變量都是const類型,即不可更改.當(dāng)使用mutable關(guān)鍵字時(shí)可取消該特性.但是對(duì)變量的更改僅在lambda表達(dá)式函數(shù)體內(nèi)生效.
異常定義
該語(yǔ)句用于聲明函數(shù)是否拋出異常,語(yǔ)法與普通函數(shù)相同。比如可以使用noexcept 表示不拋出異常。
返回類型
首先lambda表達(dá)式會(huì)根據(jù)函數(shù)體中的return 類型推測(cè)返回類型,所以返回類型的聲明并非必要。不過(guò)有時(shí)并不能推測(cè)類型,比如 return {1, 2},從一個(gè)初始化列表推測(cè)類型顯然不可能,此時(shí)你可以選擇為lambda表達(dá)式指定返回類型。返回類型前必須帶有“->"符號(hào). lambda可以返回void類型。
函數(shù)體
lambda函數(shù)體和普通函數(shù)的函數(shù)體沒(méi)有本質(zhì)區(qū)別,只不過(guò)lambda可以使用捕獲變量:
- 作用域內(nèi)的捕獲變量
- 當(dāng)在類成員函數(shù)內(nèi)使用lambda時(shí),可以捕獲到類的this指針,并以此使用類成員和成員函數(shù)。
ex:顯式值捕獲n,隱式引用捕獲m
#include <iostream>
int main()
{
int m = 0;
int n = 0;
[&, n](int a) mutable {m = ++n + a;}(4);
std::cout << m << std::endl << n << std::endl;
return 0;
}
5
0
lambda表達(dá)式使用陷阱
lambda的引用捕獲模式和隱式捕獲會(huì)使我們?cè)陂_(kāi)發(fā)時(shí)忘記捕獲變量的生命周期.
- 在使用[&]時(shí),我們?cè)趌ambda函數(shù)體內(nèi)使用的引用可能在其生效的作用域已經(jīng)銷毀.
ex:
//main.cpp
int main()
{
std::vector<std::function<bool(int)>> filters;//過(guò)濾函數(shù)
//添加一個(gè)過(guò)濾函數(shù),過(guò)濾掉
filters.emplace_back([](int value {return value % 5 ==0;});
if(filters[0](18))
{
std::cout<< "can be diveded by 5 without remain" << std::endl;
}
return 0;
}
//此時(shí)沒(méi)有什么問(wèn)題,但是當(dāng)我們寫一個(gè)函數(shù)自動(dòng)填充過(guò)濾器時(shí)
//main.cpp
std::vector<std::function<bool(int)>> filters;//過(guò)濾函數(shù)
void add_filter()
{
int arr[3] = {1,2,3};
for(auto& i: arr)
{
filters.emplace_back([&](int value){return value%i == 0;});
}
}//此時(shí)arr已經(jīng)銷毀,i實(shí)際上已經(jīng)成為空懸引用
int main()
{
add_filter();
if(filters[1](18))//此時(shí)filters內(nèi)的func所使用的除數(shù)是空懸引用而非期望的 2
{
std::cout<< "can be diveded by 2 without remain" << std::endl;
}
return 0;
}
- lambda在類內(nèi)使用時(shí),會(huì)隱式捕獲this指針,當(dāng)我們?cè)陬愪N毀后使用lambda函數(shù)對(duì)象時(shí)實(shí)際上lambda表達(dá)式此時(shí)持有一個(gè)空懸指針.在c++11標(biāo)準(zhǔn)中, lambda只能捕獲this指針地址,而不能對(duì)其所指的對(duì)象進(jìn)行深拷貝.這往往導(dǎo)致線程安全問(wèn)題.
兩個(gè)陷阱的本質(zhì)都是lambda的生命周期長(zhǎng)于其函數(shù)體內(nèi)所使用變量的生命周期所導(dǎo)致的錯(cuò)誤.
避免此類錯(cuò)誤的手段一般是避免使用隱式捕獲以及默認(rèn)捕獲模式,在capture子句中寫明要捕獲的變量和捕獲方式,以使我們或其他合作開(kāi)發(fā)者在開(kāi)發(fā)時(shí)對(duì)其保持敏感.
以上.
reference: