lambda函數(shù)在C++11中的加入很是令人興奮,因?yàn)閘ambda函數(shù)能夠大大簡(jiǎn)化代碼復(fù)雜度(語(yǔ)法糖:利于理解具體的功能),避免實(shí)現(xiàn)調(diào)用對(duì)象。C++11的lambda函數(shù)語(yǔ)法允許在需要使用的時(shí)候進(jìn)行定義。能為等待函數(shù),例如std::condition_variable(如同4.1.1節(jié)中的例子)提供很好謂詞函數(shù),其語(yǔ)義可以用來快速的表示可訪問的變量,而非使用類中函數(shù)來對(duì)成員變量進(jìn)行捕獲。
最簡(jiǎn)單的情況下,lambda表達(dá)式就一個(gè)自給自足的函數(shù),不需要傳入函數(shù)僅依賴管局變量和函數(shù),甚至都可以不用返回一個(gè)值。這樣的lambda表達(dá)式的一系列語(yǔ)義都需要封閉在括號(hào)中,還要以方括號(hào)作為前綴:
[]{ // lambda表達(dá)式以[]開始
do_stuff();
do_more_stuff();
}(); // 表達(dá)式結(jié)束,可以直接調(diào)用
例子中,lambda表達(dá)式通過后面的括號(hào)調(diào)用,不過這種方式不常用。一方面,如果想要直接調(diào)用,可以在寫完對(duì)應(yīng)的語(yǔ)句后,就對(duì)函數(shù)進(jìn)行調(diào)用。對(duì)于函數(shù)模板,傳遞一個(gè)參數(shù)進(jìn)去時(shí)很常見的事情,甚至可以將可調(diào)用對(duì)象作為其參數(shù)傳入;可調(diào)用對(duì)象通常也需要一些參數(shù),或返回一個(gè)值,亦或兩者都有。如果想給lambda函數(shù)傳遞參數(shù),可以參考下面的lambda函數(shù),其使用起來就像是一個(gè)普通函數(shù)。例如,下面代碼是將vector中的元素使用std::cout進(jìn)行打?。?/p>
std::vector<int> data=make_data();
std::for_each(data.begin(),data.end(),[](int i){std::cout<<i<<"\n";});
返回值也是很簡(jiǎn)單的,當(dāng)lambda函數(shù)體包括一個(gè)return語(yǔ)句,返回值的類型就作為lambda表達(dá)式的返回類型。例如,使用一個(gè)簡(jiǎn)單的lambda函數(shù)來等待std::condition_variable(見4.1.1節(jié))中的標(biāo)志被設(shè)置。
清單A.4 lambda函數(shù)推導(dǎo)返回類型
std::condition_variable cond;
bool data_ready;
std::mutex m;
void wait_for_data()
{
std::unique_lock<std::mutex> lk(m);
cond.wait(lk,[]{return data_ready;}); // 1
}
lambda的返回值傳遞給cond.wait()①,函數(shù)就能推斷出data_ready的類型是bool。當(dāng)條件變量從等待中蘇醒后,上鎖階段會(huì)調(diào)用lambda函數(shù),并且當(dāng)data_ready為true時(shí),僅返回到wait()中。
當(dāng)lambda函數(shù)體中有多個(gè)return語(yǔ)句,就需要顯式的指定返回類型。只有一個(gè)返回語(yǔ)句的時(shí)候,也可以這樣做,不過這樣可能會(huì)讓你的lambda函數(shù)體看起來更復(fù)雜。返回類型可以使用跟在參數(shù)列表后面的箭頭(->)進(jìn)行設(shè)置。如果lambda函數(shù)沒有任何參數(shù),還需要包含(空)的參數(shù)列表,這樣做是為了能顯式的對(duì)返回類型進(jìn)行指定。對(duì)條件變量的預(yù)測(cè)可以寫成下面這種方式:
cond.wait(lk,[]()->bool{return data_ready;});
還可以對(duì)lambda函數(shù)進(jìn)行擴(kuò)展,比如:加上log信息的打印,或做更加復(fù)雜的操作:
cond.wait(lk,[]()->bool{
if(data_ready)
{
std::cout<<”Data ready”<<std::endl;
return true;
}
else
{
std::cout<<”Data not ready, resuming wait”<<std::endl;
return false;
}
});
雖然簡(jiǎn)單的lambda函數(shù)很強(qiáng)大,能簡(jiǎn)化代碼,不過其真正的強(qiáng)大的地方在于對(duì)本地變量的捕獲。
lambda函數(shù)使用空的[](lambda introducer)就不能引用當(dāng)前范圍內(nèi)的本地變量;其只能使用全局變量,或?qū)⑵渌狄詤?shù)的形式進(jìn)行傳遞。當(dāng)想要訪問一個(gè)本地變量,需要對(duì)其進(jìn)行捕獲。最簡(jiǎn)單的方式就是將范圍內(nèi)的所有本地變量都進(jìn)行捕獲,使用[=]就可以完成這樣的功能。函數(shù)被創(chuàng)建的時(shí)候,就能對(duì)本地變量的副本進(jìn)行訪問了。
實(shí)踐一下,看一下下面的例子:
std::function<int(int)> make_offseter(int offset)
{
return [=](int j){return offset+j;};
}
當(dāng)調(diào)用make_offseter時(shí),就會(huì)通過std::function<>函數(shù)包裝返回一個(gè)新的lambda函數(shù)體。
這個(gè)帶有返回的函數(shù)添加了對(duì)參數(shù)的偏移功能。例如:
int main()
{
std::function<int(int)> offset_42=make_offseter(42);
std::function<int(int)> offset_123=make_offseter(123);
std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
}
屏幕上將打印出54,135兩次,因?yàn)榈谝淮螐膍ake_offseter中返回,都是對(duì)參數(shù)加42的;第二次調(diào)用后,make_offseter會(huì)對(duì)參數(shù)加上123。所以,會(huì)打印兩次相同的值。
這種本地變量捕獲的方式相當(dāng)安全,所有的東西都進(jìn)行了拷貝,所以可以通過lambda函數(shù)對(duì)表達(dá)式的值進(jìn)行返回,并且可在原始函數(shù)之外的地方對(duì)其進(jìn)行調(diào)用。這也不是唯一的選擇,也可以通過選擇通過引用的方式捕獲本地變量。在本地變量被銷毀的時(shí)候,lambda函數(shù)會(huì)出現(xiàn)未定義的行為。
下面的例子,就介紹一下怎么使用[&]對(duì)所有本地變量進(jìn)行引用:
int main()
{
int offset=42; // 1
std::function<int(int)> offset_a=[&](int j){return offset+j;}; // 2
offset=123; // 3
std::function<int(int)> offset_b=[&](int j){return offset+j;}; // 4
std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 5
offset=99; // 6
std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 7
}
之前的例子中,使用[=]來對(duì)要偏移的變量進(jìn)行拷貝,offset_a函數(shù)就是個(gè)使用[&]捕獲offset的引用的例子②。所以,offset初始化成42也沒什么關(guān)系①;offset_a(12)的例子通常會(huì)依賴與當(dāng)前offset的值。在③上,offset的值會(huì)變?yōu)?23,offset_b④函數(shù)將會(huì)使用到這個(gè)值,同樣第二個(gè)函數(shù)也是使用引用的方式。
現(xiàn)在,第一行打印信息⑤,offset為123,所以輸出為135,135。不過,第二行打印信息⑦就有所不同,offset變成99⑥,所以輸出為111,111。offset_a和offset_b都對(duì)當(dāng)前值進(jìn)行了加12的操作。
塵歸塵,土歸土,C++還是C++;這些選項(xiàng)不會(huì)讓你感覺到特別困惑,你可以選擇以引用或拷貝的方式對(duì)變量進(jìn)行捕獲,并且你還可以通過調(diào)整中括號(hào)中的表達(dá)式,來對(duì)特定的變量進(jìn)行顯式捕獲。如果想要拷貝所有變量,而非一兩個(gè),可以使用[=],通過參考中括號(hào)中的符號(hào),對(duì)變量進(jìn)行捕獲。下面的例子將會(huì)打印出1239,因?yàn)閕是拷貝進(jìn)lambda函數(shù)中的,而j和k是通過引用的方式進(jìn)行捕獲的:
int main()
{
int i=1234,j=5678,k=9;
std::function<int()> f=[=,&j,&k]{return i+j+k;};
i=1;
j=2;
k=3;
std::cout<<f()<<std::endl;
}
或者,也可以通過默認(rèn)引用方式對(duì)一些變量做引用,而對(duì)一些特別的變量進(jìn)行拷貝。這種情況下,就要使用[&]與拷貝符號(hào)相結(jié)合的方式對(duì)列表中的變量進(jìn)行拷貝捕獲。下面的例子將打印出5688,因?yàn)閕通過引用捕獲,但j和k
通過拷貝捕獲:
int main()
{
int i=1234,j=5678,k=9;
std::function<int()> f=[&,j,k]{return i+j+k;};
i=1;
j=2;
k=3;
std::cout<<f()<<std::endl;
}
如果你只想捕獲某些變量,那么你可以忽略=或&,僅使用變量名進(jìn)行捕獲就行;加上&前綴,是將對(duì)應(yīng)變量以引用的方式進(jìn)行捕獲,而非拷貝的方式。下面的例子將打印出5682,因?yàn)閕和k是通過引用的范式獲取的,而j是通過拷貝的方式:
int main()
{
int i=1234,j=5678,k=9;
std::function<int()> f=[&i,j,&k]{return i+j+k;};
i=1;
j=2;
k=3;
std::cout<<f()<<std::endl;
}
最后一種方式,是為了確保預(yù)期的變量能被捕獲,在捕獲列表中引用任何不存在的變量都會(huì)引起編譯錯(cuò)誤。當(dāng)選擇這種方式,就要小心類成員的訪問方式,確定類中是否包含一個(gè)lambda函數(shù)的成員變量。類成員變量不能直接捕獲,如果想通過lambda方式訪問類中的成員,需要在捕獲列表中添加this指針,以便捕獲。下面的例子中,lambda捕獲this后,就能訪問到some_data類中的成員:
struct X
{
int some_data;
void foo(std::vector<int>& vec)
{
std::for_each(vec.begin(),vec.end(),
[this](int& i){i+=some_data;});
}
};
并發(fā)的上下文中,lambda是很有用的,其可以作為謂詞放在std::condition_variable::wait()(見4.1.1節(jié))和std::packaged_task<>(見4.2.1節(jié))中;或是用在線程池中,對(duì)小任務(wù)進(jìn)行打包。也可以線程函數(shù)的方式std::thread的構(gòu)造函數(shù)(見2.1.1),以及作為一個(gè)并行算法實(shí)現(xiàn),在parallel_for_each()(見8.5.1節(jié))中使用。