Expressions lambda en C++ : quand accélèrent-elles vraiment le code ?
\n\nBonjour, collègue ! Si tu as déjà jeté un œil sur Stack Overflow, tu as sûrement vu une question qui a recueilli des milliers de vues : "What is a lambda expression, and when should I use one?". Et ce n'est pas un hasard. Les lambdas en C++ sont un sujet qui à la fois simplifie la vie et suscite de nombreux débats sur l'optimisation et les performances.
\n\nAujourd'hui, nous ne nous contenterons pas d'une définition sèche. Nous allons comprendre comment les expressions lambda fonctionnent sous le capot, quand leur utilisation rend le code plus rapide, et quand, au contraire, elle crée des problèmes cachés. C'est parti !
\n\nQu'est-ce qu'une expression lambda ? Un regard de l'intérieur
\n\nFormellement, une expression lambda est une fonction anonyme que l'on peut définir directement sur le lieu d'appel. Mais si l'on creuse plus profondément, le compilateur transforme chaque lambda en un objet fonctionnel (foncteur) unique. Voici un exemple :
\n\n// Lambda\nauto add = [](int a, int b) { return a + b; };\n\n// Ce en quoi cela se transforme (simplifié)\nclass __lambda_1 {\npublic:\n auto operator()(int a, int b) const { return a + b; }\n};\n__lambda_1 add;\n\nPoint clé : chaque lambda a son propre type unique. Cela signifie que le compilateur peut agressivement inline les appels — contrairement aux pointeurs de fonction ou à std::function.
Performance : où les lambdas gagnent-elles ?
\n\nAllons droit au but. Voici trois scénarios où les lambdas offrent un gain de performance notable :
\n\n1. Inlining et absence d'appels indirects
\n\nLorsque tu passes une lambda à une fonction template (par exemple, std::sort), le compilateur connaît le type exact. Cela lui permet d'intégrer le code de la lambda directement dans la fonction appelante. Compare :
// Option 1 : pointeur de fonction (plus lent)\nbool compare(int a, int b) { return a < b; }\nstd::sort(vec.begin(), vec.end(), compare);\n\n// Option 2 : lambda (plus rapide)\nstd::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });\n\nDans le premier cas — appel indirect via un pointeur. Dans le second — inline direct. Sur de grands volumes de données (des millions d'éléments), la différence peut atteindre 20-30% en temps.
\n\n2. Capture de variables sans copies inutiles
\n\nLes lambdas permettent une gestion flexible de la capture. Le bon choix entre [=], [&] et des variables spécifiques influence directement les performances :
std::vector<std::string> data;\nstd::string prefix = "prefix_";\n\n// Mauvais : copie de la chaîne lourde\nstd::for_each(data.begin(), data.end(), \n [=](std::string& s) { s = prefix + s; });\n\n// Bon : capture par référence\nstd::for_each(data.begin(), data.end(), \n [&](std::string& s) { s = prefix + s; });\n\nLa capture par valeur ([=]) crée une copie de prefix à l'intérieur du foncteur. Pour std::string, cela peut être coûteux. La capture par référence ([&]) est légère.
3. Lambdas dans les algorithmes STL
\n\nLes algorithmes STL modernes (C++17/20) utilisent activement les lambdas pour les calculs parallèles. Par exemple, std::transform avec la politique d'exécution std::par :
std::vector<int> input(1000000);\nstd::vector<int> output(input.size());\n\n// Traitement parallèle\nstd::transform(std::execution::par, \n input.begin(), input.end(), output.begin(),\n [](int x) { return x * x + 2 * x + 1; });\n\nLa lambda ici est un candidat idéal pour la parallélisation, car elle n'a pas d'effets secondaires (fonction pure). Le compilateur et la bibliothèque peuvent l'exécuter en toute sécurité sur plusieurs threads.
\n\nQuand les lambdas peuvent-elles nuire aux performances ?
\n\nIl serait malhonnête de ne parler que des avantages. Il existe des situations où les lambdas ne sont pas le meilleur choix :
\n\n1. Capture lourde par valeur
\n\nSi tu captures