lambda function (tsz. lambda functions)
Egyszerűbben fogalmazva: a lambda egy olyan függvény, amit nem kell külön névvel ellátni, hanem közvetlenül a kód helyén definiálunk és használunk.
A legegyszerűbb lambda így néz ki:
( parameters ) -> return_type {
// function body
};
Mindegyik elem magyarázata:
— a lambda „fogási lista” (capture list), azaz mely külső változókat kötünk be.( parameters )
— a lambda bemeneti paraméterei (lehetnek üresek is).-> return_type
— opcionális, megadható a visszatérési típus, ha a fordító nem tudná egyértelműen.{ ... }
— maga a függvény törzse.
auto hello = () {
std::cout << "Hello, lambda!" << std::endl;
};
hello(); // Meghívás
Itt a hello
egy névtelen függvény, amit a változóhoz rendelünk, majd meghívunk.
A capture lista adja meg, hogy a lambda milyen külső változókat használhat a környezetből, és hogyan (másolat vagy referencia). Lehetőségek:
— semmilyen külső változó nem kerül be.
— minden külső változó másolatként bekerül.
— minden külső változó referencia szerint bekerül.
— x
változót másolatként, y
változót referencia szerint kötünk be.this
— a lambda belsejében az aktuális osztályobjektum pointere érhető el.
int a = 10;
int b = 20;
auto sum_by_value = () {
return a + b; // másolatban használja a és b értékét
};
auto sum_by_ref = () {
return a + b; // referencia szerint használja a és b-t
};
std::cout << sum_by_value() << std::endl; // 30
std::cout << sum_by_ref() << std::endl; // 30
a = 15;
b = 25;
std::cout << sum_by_value() << std::endl; // 30 (mert másolat)
std::cout << sum_by_ref() << std::endl; // 40 (mert referencia)
A lambda paraméterei pontosan úgy működnek, mint egy normál függvénynél.
A visszatérési típus megadható explicit módon az ->
szintaxissal, vagy implicit, ha a fordító ki tudja következtetni.
auto add = (int x, int y) -> int {
return x + y;
};
std::cout << add(3, 4) << std::endl; // 7
Ha a visszatérési típus egyszerű, elhagyható:
auto multiply = (int x, int y) {
return x * y; // a fordító int-nek veszi
};
std::cout << multiply(5, 6) << std::endl; // 30
Ha több utasítást akarunk végrehajtani:
auto greet = (std::string name) {
std::cout << "Hello, " << name << "!" << std::endl;
};
greet("Dániel");
A lambda nem sima függvény, hanem egy névtelen osztály egy példánya, amely operator()
-t definiál.
Ezért:
auto
-val kell tárolni.
A lambda kifejezések az STL algoritmusokkal nagyon hasznosak.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end(), (int a, int b) {
return a > b; // csökkenő sorrend
});
for (int n : v) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Ez a példa lambda-t használ az egyéni rendezési feltételhez.
std::vector<int> numbers = {1,2,3,4,5,6,7,8,9,10};
int count_even = 0;
std::for_each(numbers.begin(), numbers.end(), (int x){
if (x % 2 == 0) {
++count_even;
}
});
std::cout << "Páros számok száma: " << count_even << std::endl;
Alapértelmezés szerint, ha a capture másolat (), akkor a lambda testén belül a captured változók nem módosíthatók, mert implicit
const
a operator()
.
Ha módosítani szeretnénk a capture-ölt másolatokat, használhatjuk a mutable
kulcsszót.
int x = 10;
auto f = () mutable {
x += 5;
std::cout << "Lambda belsejében x: " << x << std::endl;
};
f(); // kiírja: 15
std::cout << "Külső x: " << x << std::endl; // kiírja: 10 (külső változó nem változik)
A mutable
lehetővé teszi, hogy a lambda a captured változó másolatát módosítsa, de nem változtatja meg a külső változót.
Lambdát visszaadó függvényt is írhatunk.
auto make_adder(int n) {
return (int x) {
return x + n;
};
}
int main() {
auto add5 = make_adder(5);
std::cout << add5(10) << std::endl; // 15
}
std::function
Ha lambdát szeretnénk például eltárolni olyan változóban, ami kompatibilis bármilyen hasonló függvénnyel, használhatjuk az std::function
-t.
#include <functional>
std::function<int(int,int)> func = (int a, int b) {
return a + b;
};
std::cout << func(3,4) << std::endl; // 7
Az std::function
típus viszont lassabb lehet, mert dinamikus diszpatchet (virtuális függvénytábla hívást) használ.
this
capture:Osztályon belül lambda-t írva elérhetjük az objektum tagjait így:
class MyClass {
int val = 42;
public:
void foo() {
auto lam = () {
std::cout << val << std::endl;
};
lam();
}
};
int a = 10;
auto f = () { a += 5; };
f();
std::cout << a << std::endl; // 15
Lambda-n belül bármennyi sor lehet:
auto complex = (int x) {
if (x > 0) {
std::cout << "Pozitív" << std::endl;
} else {
std::cout << "Nem pozitív" << std::endl;
}
};
Fontos, hogy ha cikluson belül hozunk létre lambdákat, akkor a capture-nek meg kell felelnie annak, amit elvárunk. Például:
std::vector<std::function<void()>> funcs;
for (int i = 0; i < 3; ++i) {
funcs.push_back(() { std::cout << i << std::endl; });
}
for (auto& f : funcs) {
f(); // 0 1 2 kiíródik
}
Ha nem capture-eljük i
-t értékként, hanem referenciaként, a lambda mindhárom esetben ugyanazt a i
változót nézné, ami a ciklus után 3 lesz, tehát mindhárom lambda 3-at írna ki.
Előnyök:
Hátrányok:
auto
vagy std::function
használható.std::function
használata lassabb lehet.
mutable
kulcsszó lehetővé teszi a másolatként capture-ölt változók módosítását.auto
vagy std::function
kell a tárolásához.