Fluent C++:使用lambda讓代碼更有表現(xiàn)力

原文

Lambda可以說(shuō)是C ++ 11語(yǔ)言中最著名的功能之一。 它是一種有用的工具轿塔,但必須確保正確使用它們特愿,以使代碼更具表現(xiàn)力,而不是晦澀難懂勾缭。

image.png

首先,讓我們明確一點(diǎn)目养,lambda不會(huì)為語(yǔ)言添加功能俩由。 使用lambda可以執(zhí)行的所有操作都可以使用函子來(lái)完成娇跟,盡管語(yǔ)法更繁重且要敲更多代碼红碑。

例如,這是檢查一個(gè)int集合的所有元素是否包含在另外兩個(gè)int a和b之間的比較示例:

函子版本:

class IsBetween
{
public:
    IsBetween(int a, int b) : a_(a), b_(b) {}
    bool operator()(int x) { return a_ <= x && x <= b_; }
private:
    int a_;
    int b_;
};

bool allBetweenAandB = std::all_of(numbers.begin(), numbers.end(), IsBetween(a, b));

lambda版本:

bool allBetweenAandB = std::all_of(numbers.begin(), numbers.end(),
       [a,b](int x) { return a <= x && x <= b; });

顯然呈驶,lambda版本更簡(jiǎn)潔努释,更容易鍵入碘梢,這可能可以解釋為什么大肆宣傳C ++中加入了lambda。

對(duì)于檢查數(shù)字是否在兩個(gè)邊界之間這樣的簡(jiǎn)單處理伐蒂,我想許多人都同意應(yīng)優(yōu)先選擇lambda煞躬。 但我想證明并非所有情況都如此。

除了輸入少和簡(jiǎn)潔之外逸邦,上一個(gè)示例中的lambda和函子之間的兩個(gè)主要區(qū)別是:

  • lambda沒(méi)有名字恩沛,
  • Lambda不會(huì)在調(diào)用處隱藏其代碼。

但是缕减,通過(guò)調(diào)用具有有意義名稱的函數(shù)將代碼從調(diào)用處刪除是管理抽象級(jí)別的基本技術(shù)雷客。 但是上面的示例也還可以,因?yàn)檫@兩個(gè)表達(dá)式:

IsBetween(a, b)

[a,b](int x) { return a <= x && x <= b; }

讀起來(lái)差不多桥狡。 它們處于相同的抽象級(jí)別(盡管可以爭(zhēng)辯說(shuō)第一個(gè)表達(dá)式包含較少的噪音)搅裙。

但是,當(dāng)代碼更加詳細(xì)時(shí)裹芝,結(jié)果可能會(huì)非常不同部逮,如以下示例所示。

讓我們考慮一個(gè)代表盒子的類的示例局雄,該類可以根據(jù)其尺寸以及其材料(金屬甥啄,塑料,木材等)構(gòu)造而成炬搭,并可以訪問(wèn)該盒子的特征:

class Box
{
public:
    Box(double length, double width, double height, Material material);
    double getVolume() const;
    double getSidesSurface() const;
    Material getMaterial() const;
private:
    double length_;
    double width_;
    double height_;
    Material material_;
};

我們有一個(gè)盒子的容器:

std::vector<Box> boxes = ....

我們希望選擇足夠堅(jiān)固的盒子來(lái)容納某種產(chǎn)品(水蜈漓,油穆桂,果汁等)。

通過(guò)一點(diǎn)物理推理融虽,我們將產(chǎn)品施加在盒子四個(gè)側(cè)面上的強(qiáng)度近似為產(chǎn)品的重量享完。 如果材料可以承受施加在其上的壓力,則該盒子足夠堅(jiān)固有额。

假設(shè)該材料可以提供可以承受的最大壓力:

class Material
{
public:
    double getMaxPressure() const;
    ....
};

該產(chǎn)品提供其密度以計(jì)算其重量:

class Product
{
public:
    double getDensity() const;
    ....
};

現(xiàn)在要選擇足以容納產(chǎn)品的盒子般又,我們可以使用帶有l(wèi)ambda的STL編寫(xiě)以下代碼:

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes),
    [product](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    });

這是等效的函子定義:

class Resists
{
public:
    explicit Resists(const Product& product) : product_(product) {}
    bool operator()(const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product_.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    }
private:
    Product product_;
};

然后在主干代碼上:

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), Resists(product));

盡管函子仍然涉及更多的鍵盤(pán)輸入,但是用函子與用lambda相比巍佑,使用算法庫(kù)的這一行應(yīng)該清晰得多茴迁。不幸的是,對(duì)于lambdas版本萤衰,這一行更為重要(但不清晰)堕义,因?yàn)樗侵饕a,你和其他開(kāi)發(fā)人員需要讀這些代碼來(lái)了解它做了什么脆栋。

在這里倦卖,lambda的問(wèn)題在于顯示如何執(zhí)行檢查,而不是僅僅說(shuō)執(zhí)行了檢查椿争,因此它的抽象級(jí)別太低了怕膛。在此示例中,它損害了代碼的可讀性秦踪,因?yàn)樗仁棺x者深入研究lambda的主體以弄清楚它的作用褐捻,而不僅僅是聲明它的作用。

在這里洋侨,有必要從調(diào)用處隱藏代碼舍扰,并在其上貼上有意義的名稱。函子在這方面做得更好希坚。

但是是說(shuō)我們?cè)谌魏吻闆r下都不應(yīng)該使用lambda嗎边苹?當(dāng)然不會(huì)。

與函子相比裁僧,Lambda變得更輕便个束,更方便,你實(shí)際上可以從中受益聊疲,同時(shí)仍然保持抽象級(jí)別的井井有條茬底。這里的技巧是通過(guò)使用中介函數(shù)將lambda的代碼隱藏在有意義的名稱后面。這是在C ++ 14中執(zhí)行的方法:

auto resists(const Product& product)
{
    return [product](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

在這里获洲,lambda封裝在一個(gè)函數(shù)中阱表,該函數(shù)只是創(chuàng)建并返回它。 此功能的作用是將lambda隱藏在有意義的名稱后面。

這是主要代碼最爬,減輕了實(shí)現(xiàn)負(fù)擔(dān):

std::vector<Box> goodBoxes;
std::copy_if(boxes.begin(), boxes.end(), std::back_inserter(goodBoxes), resists(product));

現(xiàn)在涉馁,在本文的其余部分中,我們使用range而不是STL迭代器來(lái)獲得更具表現(xiàn)力的代碼:

auto goodBoxes = boxes | ranges::view::filter(resists(product));

當(dāng)算法調(diào)用的周?chē)€有其他代碼時(shí)爱致,隱藏實(shí)現(xiàn)的必要性變得更加重要烤送。為了說(shuō)明這一點(diǎn),讓我們?cè)黾右粋€(gè)要求糠悯,即這些盒子必須用以逗號(hào)分隔的測(cè)量文字說(shuō)明(例如“ 16,12.2,5”)和唯一的材料來(lái)初始化帮坚。

如果我們直接調(diào)用即時(shí)lambda,結(jié)果將如下所示:

auto goodBoxes = boxesDescriptions
  | ranges::view::transform([material](std::string const& textualDescription)
    {
        std::vector<std::string> strSizes;
        boost::split(strSizes, textualDescription, [](char c){ return c == ','; });
        const auto sizes = strSizes | ranges::view::transform([](const std::string& s) {return std::stod(s); });
        if (sizes.size() != 3) throw InvalidBoxDescription(textualDescription);
        return Box(sizes[0], sizes[1], sizes[2], material);
    })
  | ranges::view::filter([product](Box const& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    });

這真的很難閱讀互艾。

但是通過(guò)使用中介函數(shù)封裝lambda试和,代碼將變?yōu)椋?/p>

auto goodBoxes = textualDescriptions | ranges::view::transform(createBox(material))
                                     | ranges::view::filter(resists(product));

在我看來(lái),這就是你希望代碼看起來(lái)像的樣子忘朝。

請(qǐng)注意灰署,此技術(shù)在C ++ 14中有效,但在需要稍作更改的C ++ 11中無(wú)效局嘁。

lambda的類型不是由標(biāo)準(zhǔn)指定的,而是由編譯器實(shí)現(xiàn)的晦墙。 在這里悦昵,將auto作為返回類型可以使編譯器將函數(shù)的返回類型編寫(xiě)為lambda類型。 盡管在C ++ 11中無(wú)法做到這一點(diǎn)晌畅,所以您需要指定一些返回類型但指。 Lambda可通過(guò)正確的類型參數(shù)隱式轉(zhuǎn)換為std :: function,并且可以在STL和range算法中使用抗楔。 請(qǐng)注意棋凳,正如Antoine在評(píng)論部分中指出的那樣,std :: function會(huì)產(chǎn)生與堆分配和虛擬調(diào)用間接相關(guān)的額外費(fèi)用连躏。

在C ++ 11中剩岳,resists函數(shù)的建議代碼為:

std::function<bool(const Box&)> resists(const Product& product)
{
    return [product](const Box& box)
    {
        const double volume = box.getVolume();
        const double weight = volume * product.getDensity();
        const double sidesSurface = box.getSidesSurface();
        const double pressure = weight / sidesSurface;
        const double maxPressure = box.getMaterial().getMaxPressure();
        return pressure <= maxPressure;
    };
}

請(qǐng)注意,在C ++ 11和C ++ 14的實(shí)現(xiàn)中入热,都可能沒(méi)有在resists函數(shù)返回時(shí)對(duì)lambda做任何拷貝拍棕,因?yàn)榉祷刂祪?yōu)化可能會(huì)將其優(yōu)化掉。 還請(qǐng)注意勺良,返回auto的函數(shù)必須在其調(diào)用位置可見(jiàn)其定義绰播。 因此,此技術(shù)最適合與調(diào)用代碼在同一文件中定義的lambda尚困。

結(jié)論

  • 使用在其調(diào)用處定義的匿名lambda來(lái)實(shí)現(xiàn)對(duì)于抽象級(jí)別透明的函數(shù)

  • 否則蠢箩,將您的lambda封裝在中介函數(shù)中。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谬泌,隨后出現(xiàn)的幾起案子滔韵,更是在濱河造成了極大的恐慌,老刑警劉巖呵萨,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏属,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡潮峦,警方通過(guò)查閱死者的電腦和手機(jī)囱皿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)忱嘹,“玉大人嘱腥,你說(shuō)我怎么就攤上這事【性茫” “怎么了齿兔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)础米。 經(jīng)常有香客問(wèn)我分苇,道長(zhǎng),這世上最難降的妖魔是什么屁桑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任医寿,我火速辦了婚禮,結(jié)果婚禮上蘑斧,老公的妹妹穿的比我還像新娘靖秩。我一直安慰自己,他們只是感情好竖瘾,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布沟突。 她就那樣靜靜地躺著,像睡著了一般捕传。 火紅的嫁衣襯著肌膚如雪惠拭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,457評(píng)論 1 311
  • 那天乐横,我揣著相機(jī)與錄音求橄,去河邊找鬼。 笑死葡公,一個(gè)胖子當(dāng)著我的面吹牛罐农,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播催什,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼涵亏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起气筋,我...
    開(kāi)封第一講書(shū)人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拆内,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后宠默,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體麸恍,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年搀矫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抹沪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瓤球,死狀恐怖融欧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卦羡,我是刑警寧澤噪馏,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站绿饵,受9級(jí)特大地震影響欠肾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拟赊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一董济、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧要门,春花似錦、人聲如沸廓啊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谴轮。三九已至炒瘟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間第步,已是汗流浹背疮装。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粘都,地道東北人廓推。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像翩隧,于是被迫代替她去往敵國(guó)和親樊展。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容