User Tools

Site Tools


cpp:lamdas

This is an old revision of the document!


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;
    }
};
cpp/lamdas.1779401671.txt.gz · Last modified: 2026/05/21 22:14 by gthanos