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