Λάμδας & Λειτουργικός Προγραμματισμός
Κύρια λάμδα C++ — καταγραφή λιστών, μεταβλητά λάμδα, γενικά λάμδα και χρήση τους ως κατηγορήματα αλγορίθμων. Μάθετε std::function, std::invoke και το μοτίβο IIFE.
Εκφράσεις λάμδα
Μια παράσταση λάμδα δημιουργεί ένα ανώνυμο αντικείμενο συνάρτησης (κλείσιμο) ενσωματωμένο. Τα λάμδα είναι ο κύριος τρόπος μετάδοσης συμπεριφοράς σε αλγόριθμους STL, επιστροφές κλήσης και χειριστές συμβάντων.
Η πλήρης σύνταξη είναι:
[captures](parameters) mutable -> return_type { body }- Captures — variables from the enclosing scope to use inside the lambda
- Parameters — like regular function parameters
- mutable — allows modifying captured-by-value variables
- Return type — usually deduced automatically
- Body — the function body
Lambdas compile down to compiler-generated function objects (structs with operator()) — έχουν μηδενικό κόστος σε σύγκριση με χειρόγραφους συντελεστές.
Βασική σύνταξη λάμδα και συλλήψεις
Η λίστα λήψης είναι το καθοριστικό χαρακτηριστικό του λάμδα. Καθορίζει ποιες περικλείουσες μεταβλητές μπορεί να έχει πρόσβαση το λάμδα και πώς.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> nums = {1, 5, 3, 8, 2, 9, 4, 7, 6};
// Simplest lambda — no captures
std::sort(nums.begin(), nums.end(),
[](int a, int b) { return a > b; }); // descending
// Capture by value [=] — copies variables
int threshold = 5;
auto above = std::count_if(nums.begin(), nums.end(),
[threshold](int x) { return x > threshold; });
std::cout << "Above " << threshold << ": " << above << '\n';
// Capture by reference [&] — references variables
int sum = 0;
std::for_each(nums.begin(), nums.end(),
[&sum](int x) { sum += x; });
std::cout << "Sum: " << sum << '\n';
// Mixed captures
int min_val = 3, max_val = 7;
auto in_range = std::count_if(nums.begin(), nums.end(),
[min_val, &max_val](int x) { return x >= min_val && x <= max_val; });
// min_val captured by value, max_val captured by reference
// Capture all by value [=] or all by reference [&]
auto print_all = [&]() { // captures everything by reference
for (int n : nums) std::cout << n << ' ';
std::cout << '\n';
};
print_all();
// Init capture (C++14) — create new variables in the capture
auto counter = [count = 0]() mutable { return ++count; };
std::cout << counter() << '\n'; // 1
std::cout << counter() << '\n'; // 2
std::cout << counter() << '\n'; // 3
}Γενικό και Πρότυπο Λάμδα
Το C++14 εισήγαγε γενικά λάμδα με παραμέτρουςauto. Το C++20 επέκτεινε αυτό με σαφείς λίστες παραμέτρων προτύπων, επιτρέποντας πλήρη ισχύ προτύπου σε λάμδα.
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
#include <type_traits>
int main() {
// C++14: generic lambda — auto parameters
auto print = [](const auto& x) {
std::cout << x << '\n';
};
print(42); // int
print(3.14); // double
print("hello"); // const char*
// C++14: generic lambda for comparisons
auto max_of = [](const auto& a, const auto& b) {
return (a > b) ? a : b;
};
std::cout << max_of(3, 7) << '\n'; // 7
std::cout << max_of("abc", "xyz") << '\n'; // xyz (lexicographic)
// C++20: template lambda — explicit template parameters
auto add = []<typename T>(T a, T b) -> T {
return a + b;
};
std::cout << add(1, 2) << '\n'; // 3
std::cout << add(1.5, 2.5) << '\n'; // 4.0
// C++20: constrained template lambda with concepts
auto integral_only = []<std::integral T>(T x) {
return x * 2;
};
std::cout << integral_only(5) << '\n'; // 10
// integral_only(3.14); // ERROR: double doesn't satisfy std::integral
// Using generic lambdas with algorithms
std::vector<std::string> words = {"banana", "apple", "cherry"};
std::sort(words.begin(), words.end(),
[](const auto& a, const auto& b) {
return a.size() < b.size(); // sort by length
});
for (const auto& w : words) std::cout << w << ' '; // apple banana cherry
std::cout << '\n';
}std::function και std::invoke
Τοstd::functionείναι ένα καλούμενο περιτύλιγμα με διαγραφή τύπου που μπορεί να χωρέσει οποιοδήποτε καλούμενο με αντίστοιχη υπογραφή — λάμδα, δείκτες συνάρτησης, συντελεστές, δείκτες συνάρτησης μέλους. Τοstd::invokeείναι ένας ομοιόμορφος τρόπος κλήσης οποιουδήποτε καλούμενου.
#include <functional>
#include <iostream>
#include <string>
#include <vector>
// Regular function
int add(int a, int b) { return a + b; }
struct Multiplier {
int factor;
int operator()(int x) const { return x * factor; }
int multiply(int x) const { return x * factor; }
};
int main() {
// std::function can hold different callable types
std::function<int(int, int)> op;
op = add; // function pointer
std::cout << op(3, 4) << '\n'; // 7
op = [](int a, int b) { return a * b; }; // lambda
std::cout << op(3, 4) << '\n'; // 12
// Store callables in a collection
std::vector<std::function<int(int)>> transforms;
transforms.push_back([](int x) { return x + 1; });
transforms.push_back([](int x) { return x * 2; });
transforms.push_back(Multiplier{10}); // functor
int val = 5;
for (auto& fn : transforms) {
val = fn(val);
}
std::cout << val << '\n'; // ((5+1)*2)*10 = 120
// std::invoke — uniform call syntax for any callable
Multiplier m{3};
std::cout << std::invoke(add, 10, 20) << '\n'; // 30
std::cout << std::invoke(m, 7) << '\n'; // 21
std::cout << std::invoke(&Multiplier::multiply, m, 7) << '\n'; // 21
std::cout << std::invoke(&Multiplier::factor, m) << '\n'; // 3 (data member)
}Το μοτίβο IIFE
Το IIFE (Immediately Invoked Function Expression) είναι ένα μοτίβο δανεισμένο από την JavaScript. Ορίζεις ένα λάμδα και το φωνάζεις αμέσως. Αυτό είναι χρήσιμο για σύνθετη προετοιμασία μεταβλητώνconst:
const auto config = [&]() {
Config c;
c.host = read_env("HOST");
c.port = parse_int(read_env("PORT"));
if (c.port <= 0) c.port = 8080;
c.debug = is_debug_build();
return c;
}(); // <-- note the () — invoked immediatelyWithout IIFE, you would either need a non-const variable (losing immutability guarantees) or a separate factory function. IIFE keeps the initialization logic local and the result const.
Η σύλληψη με αναφορά είναι επικίνδυνη όταν το λάμδα υπερβεί τη μεταβλητή που έχει καταγραφεί:
std::function<int()> make_counter() {
int count = 0;
return [&count]() { return ++count; }; // BUG: count is destroyed!
}The returned lambda holds a reference to a local variable that no longer exists. This is undefined behavior. Fix it by capturing by value (with mutable if needed):
std::function<int()> make_counter() {
int count = 0;
return [count]() mutable { return ++count; }; // OK: owns a copy
}Εμπειρικός κανόνας: σύλληψη με αναφορά για λάμδα μικρής διάρκειας (κατηγορήματα αλγορίθμων), σύλληψη βάσει τιμής για λάμδα μεγάλης διάρκειας (επιστροφές κλήσης, αποθηκευμένες σε δομές δεδομένων).
Τοstd::functionέχει επιβάρυνση: εκχωρεί μνήμη (για μεγάλα κλεισίματα), χρησιμοποιεί εικονική αποστολή και αποτρέπει την ενσωμάτωση. Για κατηγορήματα αλγορίθμου και άλλες περιπτώσεις όπου το καλούμενο χρησιμοποιείται αμέσως, περάστε το λάμδα απευθείας — ο μεταγλωττιστής μπορεί να το ενσωματώσει πλήρως.
Χρησιμοποιήστε τοstd::functionμόνο όταν χρειάζεται:
- Αποθηκεύστε καλέσιμα για μελλοντική χρήση
- Διαγράψτε διαφορετικούς καλούμενους τύπους σε ένα ομοιόμορφο δοχείο
- Περάστε καλέσιμα πέρα από τα όρια ABI
Στα πρότυπα, χρησιμοποιήστεautoή έννοιες για να αποδεχτείτε καλέσιμα χωρίς διαγραφή τύπου:
template<std::invocable<int> F>
void apply(F&& fn, int value) {
fn(value); // no std::function overhead
}- Τα λάμδα είναι ανώνυμα αντικείμενα συνάρτησης μηδενικής επιβάρυνσης — χρησιμοποιήστε τα ελεύθερα με αλγόριθμους STL
- Λήψη κατά τιμή
[x]για ασφάλεια με λάμδα μεγάλης διάρκειας. λήψη με αναφορά[&x]για βραχύβια - Τα γενικά λάμδα C++14 (παράμετροι
auto) και τα πρότυπα λάμδα C++20 παρέχουν πλήρη γενική ισχύ προγραμματισμού - Ο τύπος
std::functionσβήνει τα καλούμενα αλλά έχει επιβάρυνση — προτιμήστεautoή πρότυπα όταν είναι δυνατόν - Το μοτίβο IIFE
[&]() { ... }()επιτρέπει την σύνθετη προετοιμασία των μεταβλητώνconst - Οι αρχικές λήψεις (
[x = expr]) επιτρέπουν τη δημιουργία νέων μεταβλητών στο κλείσιμο (C++14)
Κουίζ — Δοκιμάστε τις γνώσεις σας
(15 XP)1. Τι κάνει η λέξη-κλειδί`mutable`σε ένα λάμδα;
2. Ποιος είναι ο κίνδυνος καταγραφής μιας τοπικής μεταβλητής με αναφορά σε ένα λάμδα που επιστρέφεται από μια συνάρτηση;
3. Τι δείχνει το`[count = 0]() mutable { return ++count; }`;