Τι είναι η έκφραση λάμδα στη C++11; Πότε μπορώ να τη χρησιμοποιήσω; Ποια κατηγορία προβλημάτων επιλύουν που δεν ήταν δυνατή πριν από την εισαγωγή τους;
Μερικά παραδείγματα και περιπτώσεις χρήσης θα ήταν χρήσιμα.
Η C++ περιλαμβάνει χρήσιμες γενικές συναρτήσεις όπως οι std::for_each
και std::transform
, οι οποίες μπορεί να είναι πολύ χρήσιμες. Δυστυχώς, μπορεί επίσης να είναι αρκετά δυσκίνητες στη χρήση τους, ιδιαίτερα αν ο functor που θέλετε να εφαρμόσετε είναι μοναδικός για τη συγκεκριμένη συνάρτηση.
#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);
}
Αν χρησιμοποιείτε την f
μόνο μία φορά και σε αυτό το συγκεκριμένο σημείο, φαίνεται υπερβολικό να γράφετε μια ολόκληρη κλάση μόνο και μόνο για να κάνετε κάτι ασήμαντο και μοναδικό.
Στη C++03 θα μπορούσατε να μπείτε στον πειρασμό να γράψετε κάτι σαν το παρακάτω, για να διατηρήσετε τον τελεστή τοπικά:
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
ωστόσο αυτό δεν επιτρέπεται, το f
δεν μπορεί να περάσει σε μια συνάρτηση [template][1] στη C++03.
Η C++11 εισάγει τα lambdas που σας επιτρέπουν να γράψετε έναν inline, ανώνυμο συναρτησιακό για να αντικαταστήσετε το struct f
. Για μικρά απλά παραδείγματα αυτό μπορεί να είναι πιο καθαρό στην ανάγνωση (κρατάει τα πάντα σε ένα μέρος) και ενδεχομένως πιο απλό στη συντήρηση, για παράδειγμα στην απλούστερη μορφή:
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
Οι συναρτήσεις λάμδα είναι απλά συντακτική ζάχαρη για ανώνυμους συναρτησιακούς παράγοντες.
Σε απλές περιπτώσεις ο τύπος επιστροφής της λάμδα προκύπτει για εσάς, π.χ:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
ωστόσο όταν αρχίζετε να γράφετε πιο σύνθετες λάμδα θα συναντήσετε γρήγορα περιπτώσεις όπου ο τύπος επιστροφής δεν μπορεί να εξαχθεί από τον μεταγλωττιστή, π.χ.:
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;
}
});
}
Για να το επιλύσετε αυτό, σας επιτρέπεται να καθορίσετε ρητά έναν τύπο επιστροφής για μια συνάρτηση λάμδα, χρησιμοποιώντας -> 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;
}
});
}
Μέχρι στιγμής δεν'έχουμε χρησιμοποιήσει τίποτα άλλο εκτός από αυτό που έχει περάσει στη λάμδα μέσα σε αυτήν, αλλά μπορούμε να χρησιμοποιήσουμε και άλλες μεταβλητές, μέσα στη λάμδα. Αν θέλετε να αποκτήσετε πρόσβαση σε άλλες μεταβλητές, μπορείτε να χρησιμοποιήσετε τη ρήτρα σύλληψης (το []
της έκφρασης), η οποία μέχρι στιγμής δεν έχει χρησιμοποιηθεί σε αυτά τα παραδείγματα, π.χ.:
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;
}
});
}
Μπορείτε να συλλάβετε τόσο με αναφορά όσο και με τιμή, τις οποίες μπορείτε να καθορίσετε χρησιμοποιώντας &
και =
αντίστοιχα:
[&epsilon]
σύλληψη με αναφορά[&]
συλλαμβάνει όλες τις μεταβλητές που χρησιμοποιούνται στη λάμδα με αναφορά[=]
συλλαμβάνει όλες τις μεταβλητές που χρησιμοποιούνται στη λάμδα με τιμή[&, epsilon]
συλλαμβάνει μεταβλητές όπως με [&], αλλά το epsilon με τιμή[=, &epsilon]
συλλαμβάνει μεταβλητές όπως με [=], αλλά το epsilon με αναφοράΟ παραγόμενος operator()
είναι const
από προεπιλογή, με το συμπέρασμα ότι οι συλλήψεις θα είναι const
όταν έχετε πρόσβαση σε αυτές από προεπιλογή. Αυτό έχει ως αποτέλεσμα κάθε κλήση με την ίδια είσοδο να παράγει το ίδιο αποτέλεσμα, ωστόσο μπορείτε να [επισημάνετε τη λάμδα ως mutable
][2] για να ζητήσετε ο operator()
που παράγεται να μην είναι const
.