Nun aber steht nach gut zwölf Jahren ohne nennenswerte Veränderungen (abgesehen von einer kleinen Korrektur im Jahre 2003) ein neuer C++-Standard kurz vor der Verabschiedung durch das ISO-Standardisierungskomitee (ISO/IEC JTC1/SC22/WG21). Der derzeitige Arbeitstitel C++0x bezieht sich auf den gescheiterten Plan, den neuen Standard in den Jahren 2008/2009 zu verabschieden. Eine Annahme durch das ISO-Gremium bis zum Jahr 2011 gilt jedoch mittlerweile als relativ sicher, so daß der Standard wmöglicherweise den Namen C++11 tragen wird.
Neu in C++0X: Lambda-Ausdrücke
Neben vielen anderen Neuerungen unterstützt C++0x auch die sogenannten Lambda-Ausdrücke (engl.: lambda expressions). Hierbei handelt es sich um anonyme Funktionsobjekte, also: Funktionen oder Unterroutinen ohne Namen. Diese Funktionen sind somit auch nur an einer Stelle nutzbar, nämlich genau an der Stelle, wo man sie definiert.
Gut und schön…nur stellt sich sicherlich nun der Eine oder Andere die berechtigte Frage: wofür sind anonyme Funktionen gut, die ich nur einmal an einer Stelle definiere? Eigentlich sollte Code doch so gestaltet sein, das man Funktionalität kapselt und somit einfach wiederverwenden kann.
Um den Nutzen von Lambda-Ausdrücken besser zu verstehen, muss man zunächst den Unterschied zwischen zwei Programmierparadigmen verstehen: der imperativen und der funktionalen Programmierung. Bei der imperativen Programmierung besteht ein Programm aus Sequenzen von Anweisungen (Befehle), die Zustandsänderungen herbeiführen. Mit anderen Worten: die Befehle verändern während der Programmausführung in Variablen gespeicherte Werte. Das imperative Programmierparadigma findet sich in prozeduralen und objektorientierten Sprachen wieder, z.B. C, C++, Java, C#, Pascal, etc.
In einer funktionalen Programmiersprache hingegen werden Berechnungen als Auswertung mathematischer Funktionen verstanden. Als theoretische Grundlage dient der λ-Kalkül (Lambda-Kalkül) von Alonzo Church. Funktionale Programmiersprachen bestehen ausschließlich aus Funktionsdefinitionen und Funktionsaufrufen; funktionale Programme werden durch Kompositionen von Funktionsanwendungen konstruiert. Der Schwerpunkt liegt auf der Art der gewünschten Informationen und der erforderlichen Transformationen, Zustandsänderungen sind nicht existent. Das funktionale Programmierparadigma findet sich z.B. in Haskell, Scala oder Scheme. Auch ein Makefile stellt ein funktionales Programm dar.
Beispiel
Wie bereits erwähnt, werden mit dem neuen Standard auch Lambda-Ausdrücke in die Sprache C++ mit aufgenommen. Das folgende Beispiel soll nun demonstrieren, wann und wie diese Lambda-Ausdrücke eingesetzt werden können.
Nehmen wir einmal an, wir entwickeln eine Meßsoftware, die viele Meßwerte aufnehmen und auswerten kann. Als eine mögliche Auswertung sollen z.B. alle Meßwerte herausgefiltert (entfernt) werden, die zwischen zwei Grenzen liegen. Ohne Lambda-Ausdrücke würde man zunächst ein Funktionsobjekt (Funktor) implementieren, der die Filterfunktion kapselt:
Um nun die Meßwerte, die innerhalb eines bestimmten Intervalls liegen, zu entfernen, wenden wir beispielsweise den std::remove_if Algorithmus und unser Funktionsobjekt Between auf eine mit Meßwerten gefüllte std::list wie folgt an:
Das Programm ist selbstverständlich korrekt, aber die Implementierung des Funktionsobjekts ist relativ aufwändig, da der syntaktische Overhead einer Klassendefinition erforderlich ist. Bei der Verwendung eines Lambda-Ausdrucks kann hingegen auf die explizite Implementierung eines Funktors verzichtet und das Filtern kurz, knackig und gut lesbar programmiert werden:
Tatsächlich ist es so, das der Compiler aus dem Lambda-Ausdruck implizit nichts anderes als einen Funktor macht. Doch gerade im Zusammenspiel mit STL-Algorithmen und Containern führt der deklarative Programmierstil der Lambda-Ausdrücke zu besser lesbarem Code, denn: Lambdas erlauben die Inline-Definition eines Funktionsrumpfs in genau dem Codeabschnitt, in dem er logisch verwendet wird. Und da Lambda-Ausdrücke hauptsächlich als Hilfsfunktionen gedacht sind, soll es möglichst einfach sein, sie als solche zu schreiben.
Aufbau eines Lambda-Ausdrucks
Ein Lambda-Ausdruck ist strukturell wie folgt aufgebaut:
[lambda-capture](formal_parameters)->return-type { body }
Alle Lambda-Ausdrücke fangen mit dem sog. lambda introducer (Einleitungszeichen) [] an. Diese eckigen Klammern können leer sein (Lambda-Ausdruck ohne captures), oder sie können optional lambda-captures enthalten. Mit diesen lambda-captures legt man fest, dass die Lambdafunktion in ihrem Rumpf Variablen mit in die Berechnung einbeziehen soll, die sich im einschließenden Sichtbarkeitsbereich (Scope) befinden. Man könnte also den Lamda-Ausdruck aus dem obigen Beispiel auch folgendermaßen formulieren, um einen Zugriff innerhalb des Funktionsrumpfes auf die Konstanten minValue und maxValue zu ermöglichen:
[&minValue, &maxValue](double value){ return minValue <= value && value <= maxValue; }
Neben einer expliziten Angabe einer solchen capture list dürfen auch sog. capture-defaults verwendet werden: [], [&] oder [=]. Die Zeichen & und = legen fest, wie auf die in die Berechnung mit einbezogenen Variablen zugegriffenen werden soll. Bei & erfolgt der Zugriff per Referenz, wogegen bei = zunächst eine Kopie der verwendeten Variablen angelegt wird. Leere Klammern bedeuten: kein Zugriff auf Variablen oder Konstanten, die sich im Scope befinden. Wichtig: Wird ein Lambda-Ausdruck in einer Methode verwendet, so kann im Funktionsrumpf natürlich auch auf this zugegriffen werden. Da this keine Variable ist, erfolgt der Zugriff darauf immer über eine Kopie!
Nach dem lambda introducer folgt eine formale Parameterliste, wie man sie auch von gewöhnlichen C++ Funktionen oder Methoden kennt. Sie enthält die tatsächlichen Argumente, die von der Lambdafunktion akzeptiert werden.
Es ist auch möglich, explizit einen Rückgabetyp anzugeben. Dieses geschieht nach der formalen Parameterliste und der Zeichenkombination ->.
Zu guter Letzt folgt, umschlossen von geschweiften Klammern, der Funktionsrumpf (body).
Weitere Beispiele
Es folgen nun ein paar einfache Beispiele für Lambda-Ausdrücke:
Fazit
Lambda-Ausdrücke bieten noch mehr; sie können z.B. in Variablen gespeichert werden oder auch einer Funktion als Argument übergeben werden. Fürs Erste soll's mit dieser Einführung aber erst einmal gewesen sein. Fakt für mich ist: Lambda-Ausdrücke in C++ werden die Programmierarbeit definitiv vereinfachen. Gerade im Zusammenspiel mit der Standard Template Library (STL) bieten sie Vorteile und können die Lesbarkeit von Code deutlich erhöhen.




