lambda表達(dá)式簡(jiǎn)稱(chēng)lambda式,是C++11 新添加的特性。它適合表達(dá)簡(jiǎn)短、短期調(diào)用的可調(diào)用對(duì)象。
看effective modern c++時(shí),對(duì)lambda的簡(jiǎn)單用法做個(gè)記錄。
1. lambda原型如下:
[captures](params) mutable exception attribute {body}
其中,
-
captures是在lambda函數(shù)體中可以訪問(wèn)的外部變量,有兩種默認(rèn)捕獲模式:按值捕獲和按引用捕獲 -
params指定表達(dá)式的參數(shù) -
mutable修飾符表示lambda函數(shù)可以修改捕獲的變量,并調(diào)用對(duì)象的非const方法 -
exception告訴編譯器函數(shù)是否拋異常(noexcept) -
attribute用來(lái)聲明屬性
lambda的返回值:如果函數(shù)中有return語(yǔ)句,則類(lèi)型從return后面的表達(dá)式推導(dǎo)而來(lái);如果沒(méi)有return語(yǔ)句,則類(lèi)似于void。
關(guān)于capture的捕獲模式: [a, &b] 表示a按值捕獲,b按引用捕獲;[]表示不捕獲任何外部變量
2. lambda有哪些用法?
- STL中"_if"函數(shù)族,比如
std::find_if(container.begin(), container.end(), [](int val){ return val>0})
- 自定義比較函數(shù)(std::sort, std::nthelement, std::lower_bound)
- 為
std::unique_ptr, std::shared_ptr自定義解析器 - 用lambda實(shí)現(xiàn)閉包
lambda與閉包
- 閉包是lambda表達(dá)式的運(yùn)行期對(duì)象,根據(jù)不同的捕獲模式,閉包會(huì)持有數(shù)據(jù)的副本或引用。在上面find_if函數(shù)中,閉包就是作為第三個(gè)實(shí)參在運(yùn)行期傳遞給find_if函數(shù)的。
- 閉包類(lèi)就是實(shí)例化閉包的類(lèi)。每個(gè)lambda表達(dá)式都會(huì)觸發(fā)編譯器生成獨(dú)一無(wú)二的閉包類(lèi)。而閉包中的語(yǔ)句會(huì)變成它的閉包類(lèi)成員函數(shù)的可執(zhí)行指令。
就存在期而言,lambda和閉包類(lèi)存在于編譯期,閉包存在于運(yùn)行期。
閉包屬于可調(diào)用對(duì)象,因此可以像變量一樣對(duì)它賦值。
lambda的簡(jiǎn)單使用
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main()
{
vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int threshold = 5;
// 刪除小于threshold的元素
std::remove_if(v.begin(), v.end(), [&threshold](int v){return v<threshold;});
for_each(v.begin(), v.end(), [](int v){std::cout<<v<<"\t";});
// lambda賦給變量, 只能用auto修飾
auto f1 = [](int v){ return v*2;};
// 由于lambda也是一種可調(diào)用對(duì)象
// 因此,也可以用function來(lái)接收
std::function<int(int)> f2 = [](int v){return v+1;};
std::cout<<f2(2)<<std::endl;
}
引用懸掛問(wèn)題
默認(rèn)按引用捕獲的一個(gè)可能問(wèn)題是, 引用懸掛。即按引用捕獲會(huì)導(dǎo)致包含指向局部變量的引用,當(dāng)lambda離開(kāi)局部作用域時(shí),會(huì)導(dǎo)致引用的那個(gè)變量被釋放,lambda里的那個(gè)引用發(fā)生"懸掛"。
using func_v = std::vector<std::function<bool(int)>>;
func_v filters;
...
void addFilters()
{
int threshold = 5; // 局部變量
filters.emplace_back([&threshold](int v){return v<threshold;});
}// threshold已經(jīng)被銷(xiāo)毀,但是filters中仍保留著對(duì)它的引用
filters.doSomething(); // 未定義行為
對(duì)于上面這個(gè)問(wèn)題來(lái)說(shuō),傳值足以解決問(wèn)題。但這并不表示,按值傳遞就是安全的,因?yàn)閭鬟f指針也是按值傳遞的一種。
當(dāng)我們把一個(gè)指針(按值傳遞)傳遞給lambda(其實(shí)是所有可調(diào)用對(duì)象)時(shí),你不知道當(dāng)你在這個(gè)函數(shù)里興高采烈地大展拳腳時(shí),在函數(shù)外面可能某個(gè)”不良分子“把指針指向的對(duì)象悄悄地釋放掉了。這段話(huà)是為了說(shuō)明按值捕獲也許并沒(méi)有想象中比按引用捕獲安全到哪里去。
那么怎么解決這個(gè)問(wèn)題呢?C++14提供了廣義lambda捕獲,它將你想要捕獲的變量做一個(gè)副本,然后在lambda里使用這個(gè)副本。
void addFilters()
{
int threshold = 5; // 仍是局部變量
filters.emplace_back([threshold = threshold](int v){return v<threshold;});
// [threshold = threshold]將threshold復(fù)制入閉包,lambda內(nèi)部使用副本
}