Qu'est ce qu'une lambda expression en C++11? Quand devrais-je utiliser? Quelle classe de problème-ils résoudre ce n'était pas possible avant leur introduction?
Quelques exemples et cas d'utilisation serait utile.
Qu'est ce qu'une lambda expression en C++11? Quand devrais-je utiliser? Quelle classe de problème-ils résoudre ce n'était pas possible avant leur introduction?
Quelques exemples et cas d'utilisation serait utile.
C++ comprend utile fonctions génériques comme std::for_each
et std::transform
, qui peut être très pratique. Malheureusement, ils peuvent aussi être assez pénible à utiliser, en particulier si le foncteur vous souhaitez appliquer est unique à la fonction particulière.
#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);
}
Si vous ne l'utilisez f une fois dans cet endroit spécifique, il semble exagéré d'écrire toute une classe juste pour faire quelque chose de trivial et un.
En C++03, vous pourriez être tenté d'écrire quelque chose comme, afin de garder le foncteur local:
void func2(std::vector<int>& v) {
struct {
void operator()(int) {
// do something
}
} f;
std::for_each(v.begin(), v.end(), f);
}
cependant ce n'est pas permis, f
ne peut pas être transmis à un modèle de fonction en C++03.
C++11 introduit lambdas vous permettent d'écrire une ligne, anonyme foncteur de remplacer l' struct f
. Pour les petits exemples simples, cela peut être un nettoyant pour le lire (il maintient le tout en un seul endroit et peut-être plus simple à maintenir, par exemple, dans la forme la plus simple:
void func3(std::vector<int>& v) {
std::for_each(v.begin(), v.end(), [](int) { /* do something here*/ });
}
Lambda fonctions sont tout sucre syntaxique pour les foncteurs.
Dans les cas simples, le type de retour de la lambda est déduit pour vous, par exemple:
void func4(std::vector<double>& v) {
std::transform(v.begin(), v.end(), v.begin(),
[](double d) { return d < 0.00001 ? 0 : d; }
);
}
toutefois, lorsque vous commencez à écrire plus complexe lambdas vous allez rapidement rencontrer des cas où le type de retour ne peut pas être déduite par le compilateur, par exemple:
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;
}
});
}
Pour résoudre ce que vous êtes autorisé à spécifier explicitement un type de retour d'une fonction lambda, à l'aide de -> 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;
}
});
}
Jusqu'à présent, nous n'avons pas utilisé autre chose que ce qui a été transmis à la lambda, mais nous pouvons également utiliser d'autres variables, au sein de la lambda. Si vous souhaitez accéder à d'autres variables, vous pouvez utiliser la capture de la clause ( []
de l'expression), qui a jusqu'à présent été utilisé dans ces exemples, par exemple:
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;
}
});
}
Vous pouvez les capturer à la fois de référence et de la valeur, que vous pouvez spécifier à l'aide de =
et &
:
[&epsilon]
la capture par référence[&, epsilon]
préciser que le défaut de capture est par référence et ce que nous voulons capturer[=, &epsilon]
la capture par la valeur par défaut, mais pour l' epsilon
d'utilisation de référence à la placeLe générés operator()
est const
par défaut, avec l'implication que la capture sera const
lorsque vous y accédez par défaut. Cela a pour effet que chaque appel avec la même entrée produirait le même résultat, cependant, vous pouvez marquer le lambda comme mutable
de la demande que l' operator()
qui est produit n'est pas const
.
Le C++ concept d'une fonction lambda d'origine dans le lambda-calcul et de la programmation fonctionnelle. Un lambda est une fonction sans nom qui est utile (en programmation, pas de théorie) pour les courts extraits de code qui sont impossibles à réutiliser et n'en valent pas la nommer.
En C++, une fonction lambda est définie comme ceci
[]() { } // barebone lambda
ou dans toute sa splendeur
[]() mutable -> T { } // T is the return type, still lacking throw()
[]
est la liste de capture, ()
la liste d'arguments et d' {}
le corps de la fonction.
La liste de capture définit ce qui de l'extérieur de la lambda devrait être disponible à l'intérieur du corps de la fonction, et comment. Il peut être soit:
Vous pouvez mélanger tout les ci-dessus dans une liste séparée par des virgules [x, &y]
.
La liste d'arguments est la même que dans toute autre fonction C++.
Le code qui sera exécuté lorsque le lambda est en fait appelé.
Si un lambda n'a qu'une instruction de retour, le type de retour peut être omis et a de l'implicite type d' decltype(return_statement)
.
Si un lambda est marqué mutable (par exemple, []() mutable { }
) il est possible de muter les valeurs qui ont été capturés par valeur.
La bibliothèque défini par la norme ISO avantages fortement de lambdas et soulève la facilité d'utilisation de plusieurs bars comme maintenant, les utilisateurs n'ont pas à encombrer leur code avec de petites foncteurs dans certaines accessibles portée.
En C++14 lambdas ont été élargies par les différentes propositions.
Un élément de la liste de capture peut maintenant être initialisé avec =
. Cela permet le renommage de variables et de les capturer en les déplaçant. Un exemple tiré de la norme:
int x = 4;
auto y = [&r = x, x = x+1]()->int {
r += 2;
return x+2;
}(); // Updates ::x to 6, and initializes y to 7.
et celui de Wikipédia en montrant comment capturer avec std::move
:
auto ptr = std::make_unique<int>(10); // See below for std::make_unique
auto lambda = [ptr = std::move(ptr)] {return *ptr;};
Les Lambdas peuvent maintenant être générique (auto
serait équivalent à T
ici si
T
ont un gabarit de type d'argument quelque part dans les environs du champ d'application):
auto lambda = [](auto x, auto y) {return x + y;};
C++14 permet de déduire les types de retour de chaque fonction et de ne pas le restreindre à des fonctions de la forme return expression;
. C'est également étendue à des lambdas.
Les expressions Lambda sont généralement utilisés pour encapsuler des algorithmes de sorte qu'ils peuvent être transmis à une autre fonction. Toutefois, il est possible d'exécuter un lambda immédiatement lors de la définition:
[&](){ ...your code... }(); // immediately executed lambda expression
est fonctionnellement équivalent à
{ ...your code... } // simple code block
Cela rend les expressions lambda, un outil puissant pour la refactorisation de fonctions complexes. Vous commencez par enrouler une section de code dans une fonction lambda comme indiqué ci-dessus. Le processus explicite de paramétrage peut alors être réalisée progressivement avec l'intermédiaire de test après chaque étape. Une fois que vous avez le code-block entièrement paramétrable (tel que démontré par la suppression de l' &
), vous pouvez déplacer le code à un emplacement externe et d'en faire une fonction normale.
De même, vous pouvez utiliser des expressions lambda pour initialiser des variables sur la base du résultat d'un algorithme...
int a = []( int b ){ int r=1; while (b>0) r*=b--; return r; }(5); // 5!
Comme une façon de partitionnement logique du programme, vous pourriez même trouver utile pour passer d'une expression lambda comme argument d'une autre expression lambda...
[&]( std::function<void()> algorithm ) // wrapper section
{
...your wrapper code...
algorithm();
...your wrapper code...
}
([&]() // algorithm section
{
...your algorithm code...
});
Les expressions Lambda, vous permettent également de créer nommé fonctions imbriquées*, qui peut être un moyen pratique d'éviter la double logique. En utilisant nommé lambdas également tendance à être un peu plus facile sur les yeux (par rapport à un anonyme inline lambdas) lors du passage d'un non-triviaux de la fonction en tant que paramètre à une autre fonction. Remarque: n'oubliez pas le point-virgule après l'accolade fermante.
auto algorithm = [&]( double x, double m, double b ) -> double
{
return m*x+b;
};
int a=algorithm(1,2,3), b=algorithm(4,5,6);
Si, à la suite de profilage, révèle d'importantes initialisation de surcharge pour la fonction de l'objet, vous pouvez choisir de réécrire ce que d'une fonction normale.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.