This is an old revision of the document!
Table of Contents
Lamda Expressions
Στη C++, τα Lambda Expressions εισήχθησαν στο πρότυπο C++11 και αποτελούν έναν σύντομο τρόπο για τη δημιουργία ανώνυμων συναρτήσεων (function objects) απευθείας στο σημείο όπου χρειάζονται. Είναι ιδιαίτερα χρήσιμα όταν θέλουμε να δηλώσουμε μια σύντομη λογική, χωρίς να ορίζουμε επιπλέον συναρτήσεις ή κλάσεις.
Σύνταξη των lamda expressions
Μια λάμδα έκφραση αποτελείται από τέσσερα βασικά μέρη:
[capture clause] (parameters) → return_type { body }
- [Capture Clause]: Καθορίζει ποιες μεταβλητές από το εξωτερικό περιβάλλον είναι προσβάσιμες μέσα στη lamda.
- Parameters(): Οι παράμετροι της συνάρτησης, όπως σε μια κανονική συνάρτηση περικλύονται σε παρένθεση.
- Return Type (προαιρετικό): Επιστρεφόμενος τύπος. Συνήθως ο compiler το συμπεραίνει αυτόματα.
- {Body}: Ο κώδικας που εκτελείται στο σώμα της συνάρτησης. Περικλύεται σε άγκιστρα.
Capture Clause (Δέσμευση Μεταβλητών από το εξωτερικό περιβάλλον της lamda)
Το Capture Clause επιτρέπει στη lamda να “βλέπει” μεταβλητές που ορίστηκαν έξω από αυτήν, χωρίς οι μεταβλητές αυτές να είναι παράμετροι της. Διακρίνουμε τους παρακάτω τύπους Capture Clause.
| Capture | Περιγραφή |
|---|---|
| [ ] | Καμία εξωτερική μεταβλητή δεν είναι προσβάσιμη. |
| [x] | Δέσμευση της μεταβλητής x με τιμή (αντίγραφο). |
| [&x] | Δέσμευση της μεταβλητής x με αναφορά (reference). |
| [=] | Δέσμευση όλων των τοπικών μεταβλητών με τιμή. |
| [&] | Δέσμευση όλων των τοπικών μεταβλητών με αναφορά. |
Παραδείγματα Lamda Expressions
Παράδειγμα 1ο - Φιλτράρισμα και εξαγωγή μέσου όρου
Στο παρακάτω παράδειγμα οι βαθμοί ενός φοιτητής, φιλτράρονται ως προς την προσβασιμοτητας τους και από αυτούς υπολογίζεται άθροισμα και πλήθος, ώστε να εξαχθεί ο μέσος όρος.
- filter_and_average.cpp
#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> grades = {70, 85, 40, 92, 55, 30}; int sum = 0; int count = 0; int threshold = 50; // Δέσμευση με αναφορά (&) για να τροποποιούμε τα sum και count // Δέσμευση με τιμή (threshold) γιατί μόνο το διαβάζουμε std::for_each(grades.begin(), grades.end(), [&sum, &count, threshold](int g) { if (g >= threshold) { sum += g; count++; } }); if (count > 0) { std::cout << "Average of passing grades: " << (double)sum / count << std::endl; // Έξοδος: Average of passing grades: 75.5 } }
Παρατηρούμε ότι το Capture Clause λαμβάνει τις μεταβλητές sum, count, threshold. Οι δύο πρώτες καλούνται με αναφορά και την ενημερώνουην μετά την έξοδο από τη lamda, ενώ η threshold καλείται με τιμή.
Πως διαχειρίζεται ο compiler το lambda expression;
Ο compiler μετατρέπει τη λάμδα σε μια ανώνυμη κλάση (anonymous functor). Για την παραπάνω έκφραση lamda ο compiler γράφει το εξής:
class __lambda_unique_name { private: int& sum; // Capture by reference int& count; // Capture by reference const int threshold; // Capture by value public: // Constructor για την αρχικοποίηση των captures __lambda_unique_name(int& _sum, int& _count, int _threshold) : sum(_sum), count(_count), threshold(_threshold) {} // Ο τελεστής κλήσης (το σώμα της lambda) // Είναι const εξ ορισμού, εκτός αν χρησιμοποιηθεί η λέξη 'mutable' void operator()(int g) const { if (g >= threshold) { sum += g; count++; } } // Ο compiler διαγράφει τον default constructor __lambda_unique_name() = delete; };
Παρατηρούμε ότι η μεταβλητή threshold δεν αλλάζει μέσα στην έκφραση lambda. Αν θέλαμε να αλλάζει σε διαφορετικές κλήσεις, θα έπρεπε να προσθέσουμε τη λέξη mutable.
Παράδειγμα 2ο - Αθροιστής
Ας δούμε το παρακάτω παράδειγμα που απεικόνίζει έναν αθροιστή και μεταβάλει μία μεταβλητή που έχει περαστεί με τιμή στο Clause.
- sample_accumulator.cpp
#include <iostream> #include <vector> int main() { // Το [total = 0] δημιουργεί μια εσωτερική μεταβλητή (capture by value). auto add_to_total = [total = 0](int amount) mutable { total += amount; // Η τροποποίηση επιτρέπεται μόνο λόγω του 'mutable' return total; }; std::vector<int> expenses = {10, 45, 5, 20}; std::cout << "Starting accumulation..." << std::endl; for (int bill : expenses) { std::cout << "Adding: " << bill << " | Current Total: " << add_to_total(bill) << std::endl; } }
Στο παράδειγμα, η μεταβλητή total μεταβάλει την τιμή της σε κάθε κλίση της έκφρασης lamda. Εδώ η προσθήκη της λέξης mutable είναι απαραίτητη και χωρίς αυτή το πρόγραμμα δεν μεταγλωττίζεται. H λέξη mutable σηματοδοτεί ότι η μεταβλητή στο Capture Clause μπορεί να αλλάξει τιμή. Ο λόγος που συμβαίνει αυτό είναι γιατί η λέξη mutable σηματοδοτεί η συνάρτηση υπερφόρτωσης του τελεστή () δε θα είναι const. Παρακάτω δίνουμε το functor που παράγεται για τη δεδομένη lambda έκφραση.
class __lambda_accumulator_unique { private: int total; // Το εσωτερικό state (capture by value) public: // Constructor που αρχικοποιεί το total (στην περίπτωσή μας με 0) __lambda_accumulator_unique(int initial_val) : total(initial_val) {} /** * Ο operator() ΔΕΝ είναι const. * Αυτό συμβαίνει επειδή χρησιμοποιήσαμε τη λέξη 'mutable'. * Αν έλειπε το 'mutable', η μέθοδος θα ήταν: * int operator()(int amount) const { ... } * και η γραμμή total += amount θα έβγαζε error στη μεταγλώττιση. */ int operator()(int amount) { total += amount; // Τροποποιούμε το data member της κλάσης return total; } };
