c++/cpp lambda表達(dá)式的使用

前景

在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*/
    1. capture子句
    1. 參數(shù)列表(optional)
    1. 可變規(guī)范(optional)
    1. 異常定義(optional)
    1. 返回類型(optional)
    1. 函數(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:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容