Lambda-Ausdrücke in C++: Wann beschleunigen sie den Code wirklich?
\n\nHallo Kollege! Wenn du jemals auf Stack Overflow gestöbert hast, hast du sicherlich die Frage gesehen, die tausende von Aufrufen gesammelt hat: "What is a lambda expression, and when should I use one?". Und das ist kein Zufall. Lambdas in C++ sind ein Thema, das sowohl das Leben vereinfacht als auch viele Debatten über Optimierung und Leistung auslöst.
\n\nHeute geben wir nicht nur eine trockene Definition. Wir werden verstehen, wie Lambda-Ausdrücke unter der Haube funktionieren, wann ihre Verwendung den Code schneller macht und wann sie im Gegenteil versteckte Probleme verursacht. Los geht's!
\n\nWas ist ein Lambda-Ausdruck? Ein Blick von innen
\n\nFormal ist ein Lambda-Ausdruck eine anonyme Funktion, die direkt am Aufrufort definiert werden kann. Aber wenn man tiefer gräbt, verwandelt der Compiler jedes Lambda in ein eindeutiges Funktionsobjekt (Funktor). Hier ist ein Beispiel:
\n\n// Lambda\nauto add = [](int a, int b) { return a + b; };\n\n// Vereinfachte Darstellung, in die es umgewandelt wird\nclass __lambda_1 {\npublic:\n auto operator()(int a, int b) const { return a + b; }\n};\n__lambda_1 add;\n\nDer entscheidende Punkt: Jedes Lambda hat seinen eigenen eindeutigen Typ. Das bedeutet, dass der Compiler Aufrufe aggressiv inlinen kann – im Gegensatz zu Funktionszeigern oder std::function.
Leistung: Wo gewinnen Lambdas?
\n\nKommen wir direkt zur Sache. Hier sind drei Szenarien, in denen Lambdas einen spürbaren Leistungszuwachs bringen:
\n\n1. Inlining und Fehlen indirekter Aufrufe
\n\nWenn du ein Lambda an eine Template-Funktion übergibst (z.B. std::sort), kennt der Compiler den genauen Typ. Dies ermöglicht es ihm, den Lambda-Code direkt in die aufrufende Funktion einzubetten. Vergleiche:
// Variante 1: Funktionszeiger (langsamer)\nbool compare(int a, int b) { return a < b; }\nstd::sort(vec.begin(), vec.end(), compare);\n\n// Variante 2: Lambda (schneller)\nstd::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });\n\nIm ersten Fall – indirekter Aufruf über einen Zeiger. Im zweiten – direktes Inline. Bei großen Datenmengen (Millionen von Elementen) kann der Unterschied 20-30% der Zeit betragen.
\n\n2. Erfassen von Variablen ohne unnötige Kopien
\n\nLambdas ermöglichen eine flexible Steuerung der Erfassung. Die richtige Wahl zwischen [=], [&] und bestimmten Variablen beeinflusst direkt die Leistung:
std::vector<std::string> data;\nstd::string prefix = "prefix_";\n\n// Schlecht: Kopieren eines schweren Strings\nstd::for_each(data.begin(), data.end(), \n [=](std::string& s) { s = prefix + s; });\n\n// Gut: Erfassung per Referenz\nstd::for_each(data.begin(), data.end(), \n [&](std::string& s) { s = prefix + s; });\n\nErfassung per Wert ([=]) erstellt eine Kopie von prefix innerhalb des Funktors. Für std::string kann das teuer sein. Erfassung per Referenz ([&]) ist leichtgewichtig.
3. Lambdas in STL-Algorithmen
\n\nModerne STL-Algorithmen (C++17/20) nutzen Lambdas aktiv für parallele Berechnungen. Zum Beispiel std::transform mit der Ausführungsrichtlinie std::par:
std::vector<int> input(1000000);\nstd::vector<int> output(input.size());\n\n// Parallele Verarbeitung\nstd::transform(std::execution::par, \n input.begin(), input.end(), output.begin(),\n [](int x) { return x * x + 2 * x + 1; });\n\nDas Lambda ist hier ein idealer Kandidat für die Parallelisierung, da es keine Nebenwirkungen hat (reine Funktion). Compiler und Bibliothek können es sicher auf mehreren Threads ausführen.
\n\nWann können Lambdas die Leistung beeinträchtigen?
\n\nEs wäre unfair, nur über die Vorteile zu sprechen. Es gibt Situationen, in denen Lambdas nicht die beste Wahl sind:
\n\n1. Schwere Erfassung per Wert
\n\nWenn du er