Was ich neulich im Job sah, warf mich förmlich um. Zum ersten Mal sah ich C++ lambda Ausdrücke in einem in die Jahre gekommen Softwarestapel. Es war sogar eine richtig praktische Anwendung, an die die Erfinder dieser neuen Sprachfeatures nicht gedacht hatten:
auto const fDoEvents = [&]()
ProcessMessagesForCurrentProcessMainWindow();
ProcessAfxMessagePump();
};
fDoEvents();
// vollen Refresh auslösen
xy->AddChanges(CTRL_NAV_RESULT);
z->Refresh(CTRL_NAV_CURR, true);
fDoEvents();
Das lambda fasste Statements zusammen, die in einem Spaghetticode immer wieder zu wiederholen sind. Hier war es immer mal wieder Messages zu bearbeiten. In der guten(?), alten Zeit hätten #define macros herhalten müssen.
In unmittelbarer Nähe, ein paar Zeilen unter dem zweiten Aufruf von fDoEvent() befand sich ein Kommentar mit der Diskette:
// TODO: OFL aktualisieren (Dirtyflag / Diskette zurücksetzen)
Nach dem ersten Lachen stellte sich die Frage, wie dieser Kommentar in den Source kam.
Die Datei wurde laut tfs um 2015 geschrieben.
Die zweite Frage, war die, wie lambda Ausdrücke in den neuen C++ Standards funktioneren?
Das lud mich dann ein hierzu etwas zu schreiben.
Was macht die Diskette?
Meine erste Vorstellung war die eines jungen Programmierers, der einen alten Source lesbarer machen wollte.
Wenn der Source alt wäre, wäre das richtig. Er war aber Jahrgang 2015.
Das war das Resultat einer „nimm-mal-das-da-und-pass-das-an“ Anweisung . „Das da“ hatte das Todo mit der Diskette und dem Spaghetti Code mit einer unendlichen Wiederholung immer derselben Aufrufe.
Der motivierte Programmierer hat das modernste Sprachmittel genommen um die Wiederholungen lesbarer zu machen. Warum blieb der Kommentar mit dem Todo?
- Es war ein Freelancer, der Todos nie entfernt, damit immer noch etwas zu tun blieb.
oder
- Es war jemand der den Begriff „Diskette“ gar nicht kannte.
Wie funktioneren lamdba Ausdrücke?
Der Standard liefert keine richtige Erklärung für Lambda Ausdrücke. Hilfreicher fand ich einen Blog Artikel zum Thema. Nach viel Schwärmerei beschrieb der letzte Abschnitt kurz die Implementierung und schon war mir alles klar.
In C++ haben lambdas eine einfache Semantik. Die syntaktische Erweiterung von C++ sind so gestaltet, dass der Compiler eine Klasse mit Funktionsoperator erzeugt.
Der Typ der Klasse ist anonym. Hier wird der auto Typ genutzt.
Der Programmierer implementiert die Statements der Funktion und im Konstruktor werden entweder keine Parameter übergeben ([]), oder die Bezeichner aus der Umgebung werden per Referenz ([&]) oder per Copy ([=]) zur Verfügung gestellt.
Nun noch einmal darüber nachdenken und schon ist alles klar.
In dem Kontext auf dem Sourceabschnitt werden einfach nur die beiden Windows Aufrufe ausgeführt. Die [&] Klausel hat der Programmierer per copy&past übernommen. Hier werden keine Bezeichner aus der Umgebung verwendet.
Übrigens kann man sich mit der [&] Klausel in den Fuß schießen, wenn das lambda zurückgegeben wird. Dann hat man Referenzen auf Objekte, die mit dem Stack abgeräumt wurden. Das ist eben ein Feature von C++:
Man kann sich nicht so leicht in den Fuß schießen, aber wenn, dann ist das ganze Bein weg. (frei nach Bjarne Stroustrup)