Kas yra lambda išraiška C++11? Kada galėčiau ją naudoti? Kokios klasės problemą jos išsprendžia, kurios nebuvo įmanoma išspręsti iki jų įvedimo?
Būtų naudinga pateikti keletą pavyzdžių ir panaudojimo atvejų.
C++ turi naudingų bendrųjų funkcijų, tokių kaip std::for_each
ir std::transform
, kurios gali būti labai patogios. Deja, jomis naudotis taip pat gali būti gana sudėtinga, ypač jei funktorius, kurį norite taikyti, yra unikalus konkrečiai funkcijai.
#include <algorithm>
#include <vector>
namespace {
struct f {
void operator()(int) {
// do something
}
};
}
void func(std::vector<int>& v) {
f f;
std::for_each(v.begin(), v.end(), f);
}
Jei f
naudojate tik vieną kartą ir tik toje konkrečioje vietoje, atrodo, kad rašyti visą klasę vien tam, kad atliktumėte kažką trivialaus ir vienkartinio, yra perteklinis dalykas.
C++03 gali kilti pagunda parašyti kažką panašaus į tai, kas išdėstyta toliau, kad funktorius išliktų vietinis:
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
tačiau tai neleistina, nes C++03 sistemoje f
negalima perduoti šablono funkcijai.
C++11 įvestos lambdos leidžia parašyti į eilutę įvestą anoniminį funktorių, kuris pakeistų struktą f
. Nedideliuose paprastuose pavyzdžiuose tai gali būti švariau skaitoma (viskas laikoma vienoje vietoje) ir potencialiai paprasčiau prižiūrėti, pvz:
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
Lambda funkcijos - tai tik sintaksinis cukrus anoniminiams funktoriams.
Paprastais atvejais lambda funkcijos grąžinimo tipas išvedamas už jus, pvz:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
tačiau kai pradėsite rašyti sudėtingesnes lambdas, greitai susidursite su atvejais, kai kompiliatorius negali išvesti grąžinimo tipo, pvz:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
Kad tai išspręstumėte, galite aiškiai nurodyti lambda funkcijos grąžinimo tipą, naudodami -> T
:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) -> double {
if (d < 0.0001) {
return 0;
} else {
return d;
}
});
}
Iki šiol nenaudojome nieko kito, išskyrus tai, kas buvo perduota lambda, tačiau lambda viduje galime naudoti ir kitus kintamuosius. Jei norite gauti prieigą prie kitų kintamųjų, galite naudoti užfiksavimo sąlygą (išraiškos []
), kuri kol kas šiuose pavyzdžiuose buvo nenaudojama, pvz:
void func5(std::vector<double>& v, const double& epsilon) {
std::transform(v.begin(), v.end(), v.begin(),
[epsilon](double d) -> double {
if (d < epsilon) {
return 0;
} else {
return d;
}
});
}
Galite užfiksuoti ir pagal nuorodą, ir pagal vertę, kurias galite nurodyti atitinkamai naudodami &
ir =
:
[&epsilon]
užfiksuoti pagal nuorodą[&]
visus lambda sistemoje naudojamus kintamuosius fiksuoja pagal nuorodą[=]
fiksuoja visus lambda sistemoje naudojamus kintamuosius pagal vertę[&, epsilon]
fiksuoja kintamuosius kaip ir su [&], bet epsilon pagal vertę[=, &epsilon]
perima kintamuosius kaip ir su [=], bet epsilon pagal nuorodąPagal numatytuosius nustatymus sukurtas operatorius()
yra const
, o tai reiškia, kad pagal numatytuosius nustatymus, kai prie jų prieisite, captures bus const
. Tai lemia, kad kiekvienas skambutis su ta pačia įvestimi duos tą patį rezultatą, tačiau galite pažymėti lambda kaip mutable
, norėdami paprašyti, kad sukurtas operatorius()
nebūtų const
.
C++ lambda funkcijos sąvoka kilo iš lambda skaičiuoklės ir funkcinio programavimo. Lambda - tai neįvardyta funkcija, kuri naudinga (faktiniame programavime, o ne teorijoje) trumpoms kodo atkarpoms, kurių neįmanoma pakartotinai panaudoti ir kurių neverta įvardyti.
C++ kalboje lambda funkcija apibrėžiama taip
[]() { } // barebone lambda
arba visa jos šlovė
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
yra užfiksavimo sąrašas, ()
- argumentų sąrašas, o {}
- funkcijos kūnas.
Užfiksavimo sąrašas apibrėžia, kas ir kaip iš lambda išorinės pusės turėtų būti prieinama funkcijos kūno viduje. Jis gali būti:
Bet kurį iš pirmiau išvardytų variantų galite sujungti į kableliais atskirtą sąrašą [x, &y]
.
Argumentų sąrašas yra toks pat, kaip ir bet kurioje kitoje C++ funkcijoje.
Kodas, kuris bus vykdomas, kai lambda bus iš tikrųjų iškviesta.
Jei lambda turi tik vieną grąžinimo teiginį, grąžinimo tipas gali būti praleistas ir turi numanomą tipą decltype(return_statement)
.
Jei lambda pažymėta kaip mutable (pvz., []() mutable { }
), leidžiama mutuoti reikšmes, kurios buvo užfiksuotos value.
ISO standarte apibrėžtai bibliotekai lambda duoda daug naudos ir keleriopai padidina naudojimo patogumą, nes dabar naudotojams nereikia užgriozdinti kodo mažais funktoriais tam tikroje prieinamoje srityje.
C++14 lambdos buvo išplėstos įvairiais pasiūlymais.
Dabar gaudymo sąrašo elementą galima inicializuoti su =
. Tai leidžia pervadinti kintamuosius ir užfiksuoti juos perkeliant. Pavyzdys paimtas iš standarto:
int x = 4;
auto y = [&r = x, x = x+1]()->int {
r += 2;
return x+2;
}(); // Updates ::x to 6, and initializes y to 7.
ir iš Vikipedijos paimtas pavyzdys, rodantis, kaip perimti kintamuosius naudojant std::move
:
auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};
Dabar lambdos gali būti bendrinės (auto
čia būtų lygiavertis T
, jei
T
būtų tipo šablono argumentas kur nors aplinkinėje srityje):
auto lambda = [](auto x, auto y) {return x + y;};
C++14 leidžia išvesti grąžinimo tipus kiekvienai funkcijai ir neapsiriboja tik funkcijomis, turinčiomis formą grąžinti išraišką;
. Tai taip pat taikoma ir lambdoms.
Lambda išraiškos paprastai naudojamos algoritmams užsklęsti, kad juos būtų galima perduoti kitai funkcijai. Tačiau galima lambda išraišką įvykdyti iš karto ją apibrėžus:
[&](){ ...your code... }(); // immediately executed lambda expression
funkciniu požiūriu yra lygiavertis
{ ...your code... } // simple code block
Dėl to lambda išraiškos yra galingas sudėtingų funkcijų pertvarkymo įrankis. Pradėkite nuo to, kad kodo sekciją įvilksite į lambda funkciją, kaip parodyta pirmiau. Tuomet aiškaus parametrizavimo procesą galima atlikti palaipsniui, po kiekvieno žingsnio atliekant tarpinį testavimą. Kai kodo blokas bus visiškai parametrizuotas (tai rodo &
pašalinimas), galite perkelti kodą į išorinę vietą ir paversti jį įprasta funkcija.
Panašiai galite naudoti lambda išraiškas, norėdami inicializuoti kintamuosius pagal algoritmo rezultatą...
int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!
Kaip būdas suskirstyti programos logiką, jums gali būti naudinga netgi perduoti lambda išraišką kaip argumentą kitai lambda išraiškai...
[&]( std::function<void()> algorithm ) // wrapper section
{
...your wrapper code...
algorithm();
...your wrapper code...
}
([&]() // algorithm section
{
...your algorithm code...
});
Lambda išraiškos taip pat leidžia kurti pavadintas įterptines funkcijas, o tai gali būti patogus būdas išvengti logikos dubliavimo. Be to, naudojant įvardytas lambdas paprastai būna šiek tiek paprasčiau (palyginti su anoniminėmis inline lambdomis), kai kitai funkcijai kaip parametras perduodama netriviali funkcija. Pastaba: nepamirškite kabliataškio po uždaromojo lenktinio skliausto.
auto algorithm = [&]( double x, double m, double b ) -> double
{
return m*x+b;
};
int a=algorithm(1,2,3), b=algorithm(4,5,6);
Jei vėlesnis profiliavimas atskleis dideles funkcijos objekto inicializavimo pridėtines išlaidas, galite nuspręsti tai perrašyti kaip įprastą funkciją.