Guten Morgen,
ich habe mal eine Frage über Lambda in C++.
Eigentlich programmiere ich nur in C, soll aber für C++ jetzt Misra
machen und da ist mir folgender code aufgefallen
Nein denn beim einfachen Funktionsaufruf wird die Bedingung nur einmal
beim Aufruf ausgewertet. Der Name der Funktion macht schon klar, dass
die Bedingung mehrfach geprüft werden muss. Das geht nur über eine
Funktion/Lamba.
So ganz grob macht ein Lambda das selbe wie ein Funktionspointer in C.
Neben der kompakten Schreibweise gibt eine nützliche Erweiterung, der
Lambda Ausdruck kann auf Variablen aus dem umgebenden Bereich zugreifen.
Ja, und ein weiterer Vorteil ist:
Wenn man die Funktion nur an dieser Stelle braucht, muß man nicht den
Quelltext mit einer zusätzlichen Funktion zumüllen.
Die würde an einer ganz anderen Stelle definiert werden, als sie
verwendet wird. Das erschwert das Verständnis eher.
Hannes schrieb:> Und da darf man per & capturen wenn das Lambda im return-Statement> steht?
Nicht das Lambda wird zurückgegeben, sondern der Rückgabewert von
waitFor. Solange waitFor das Lambda nicht statisch speichert und in
einem späteren Aufruf wiederverwendet, ist das überhaupt kein Problem.
Hmmm... genau da liegt das Problem.
Du siehst nicht auf den ersten Blick, ob waitFor() das MSTPSR noch
benutzen wird, nachdem die aufrufende Funktion verlassen wurde.
Und du siehst nicht auf den ersten Blick, ob so etwas funktionieren
wird. Du musst die 1000 Regeln kennen, nach denen der Compiler einen
Copy Constructor aufruft. Und du musst nachschauen, ob die MSTPSR Klasse
eine selbst erfundene Gargabe Collection Strategie implementiert.
Diese ganzen Automatismen, die C++ unter der Haube einbaut, erscheinen
auf den ersten Blick ja sinnvoll. Aber alle zusammen sind so verworren -
die Probleme aufspüren wird aufwendiger, als umfangreichen, aber
durchschaubaren C Code schreiben.
Der Opa aus der Muppet Show schrieb:> u siehst nicht auf den ersten Blick, ob waitFor() das MSTPSR noch> benutzen wird, nachdem die aufrufende Funktion verlassen wurde.
Das gleiche könnte man dann auch für jeden Pointer sagen, der an eine
Funktion übergeben wird. Soll man deshalb dann generell keine Pointer
mehr an Funktionen übergeben? Oder nur noch Pointer auf globale
Variablen, damit das Pointerziel bei theoretisch möglichen späteren
Zugriffen auch garantiert existiert?
So ähnlich denkt MISRA.... :-)
Alles, was auch in BASIC geht, ist in C erlaubt. Alles andere böse.
@Hannes: ich finde das per Referenz auch nicht elegant, und hier
unnötig.
Mit [=] hätte man in der Lambda-Funktion von allem eine Kopie, und kann
die Werte nicht ändern. Das wäre hier vielleicht angemessener.
Man müsste aber mehr vom Programm sehen.
> Soll man deshalb dann generell keine Pointer mehr an Funktionen übergeben?
Da gibt es 4 Ansichten.
K&R wollten effizienten Assemblercode in einer Hochsprache schreiben.
Mussten diese Gefahren in Kauf nehmen. Deren Lösung: Eine Sprache, bei
der man die Gefahren sofort im Quellcode sieht.
Von Lisp bis Java wollte die andere Fraktion diese Gefahren von vorn
herein vermeiden. Hat sich nicht gegen effiziente C-Programme
durchgesetzt.
Dann gibt es halt den C++ Ansatz. Compiler und Library fangen die
Gefahren unter der Haube selbst ab. Hatte bei reinen Qt Programmen
erstaunlich gut funktioniert.
Und Rust mit dem Konzept des Ownership. Klingt zwar gut, aber was willst
du machen, wenn die Libraries die du brauchst in C++ geschrieben sind?
Anscheinend haben nicht mal die Fachleute der MISRA eine Lösung. Bitten
nur mehr verzweifelt, wenn es unbedingt C++ sein muss, dann benutzt
wenigstens die gefährlichsten Features nicht mehr.
Klaus W. schrieb:> @Hannes: ich finde das per Referenz auch nicht elegant, und hier> unnötig.> Mit [=] hätte man in der Lambda-Funktion von allem eine Kopie, und kann> die Werte nicht ändern. Das wäre hier vielleicht angemessener.
... und wahrscheinlich sogar effizienter.
Der Opa aus der Muppet Show schrieb:> Und Rust mit dem Konzept des Ownership. Klingt zwar gut, aber was willst> du machen, wenn die Libraries die du brauchst in C++ geschrieben sind?
Einen Wrapper schreiben, wie bei jeder anderen Sprachschnittstelle auch.
Yalu X. schrieb:> Nicht das Lambda wird zurückgegeben, sondern der Rückgabewert von> waitFor. Solange waitFor das Lambda nicht statisch speichert und in> einem späteren Aufruf wiederverwendet, ist das überhaupt kein Problem.
Stimmt! Aber ich denke nicht das waitFor wirklich selbst wartet. Das
wäre ja Quark, denn das kann man besser schreiben. Ich vermute waitFor
dient dazu eine Art Co-Coroutine zu implementieren. Aber ja, so lange
man nichts genaueres weiß, kann man nur spekulieren ...
Auf der anderen Seite ist waitFor eventuell"sprechender" als
while(!cond) ;.
Hm.
Ich war nur überrascht das man in misra überhaupt per Referenz capturen
darf. Performance wurde schon angesprochen. Aber man soll ja immer per
value capturen, dann muss man über den ganzen Dangling Ref kramt nicht
nachdenken. Zumal n und Bit sicher nur ints sind, oder gar kürzere
Datentypen. Da braucht eine Referenz auch noch mehr Speicher 😱
Hannes schrieb:> Yalu X. schrieb:>> Nicht das Lambda wird zurückgegeben, sondern der Rückgabewert von>> waitFor. Solange waitFor das Lambda nicht statisch speichert und in>> einem späteren Aufruf wiederverwendet, ist das überhaupt kein Problem.>> Stimmt! Aber ich denke nicht das waitFor wirklich selbst wartet.
Das wäre eigentlich genau das, was ich erwarten würde. Sonst ist der
Name sehr irreführend.
> Das wäre ja Quark, denn das kann man besser schreiben.
Warum wäre es Quark, eine Funktion zu haben, die auf ein bestimmtes
Ereignis wartet, das für die weitere Ausführung des Code erforderlich
ist?
Zum Thema Lambdas würde ich mal etwas mehr ausholen:
In C (nicht C++) erlaubt gcc verschachtelte Funktionen.
Eine verschachtelte Funktion ist nichts weiter als eine normale
Top-Level-Funktion mit verstecktem Bezeichner und Zugriff auf die
lokalen Variablen der umgebenden Funktion. Dazu übergibt gcc einen
(unsichtbaren) Zeiger auf den Stackframe, kann aber auch entsprechende
Optimierungen vornehmen. Genauso tut es Turbo Pascal und ähnliche
Programmiersprachen mit verschachtelten Funktionen. Sogar auf
x86-Assemblerniveau gibt es dafür einen Befehl, nämlich "enter".
In C++ sind verschachtelte Funktionen verboten, denn dafür gibt es die
Lambdas. Das C++-Äquivalent der verschachtelten C-Funktion ist:
1
autofuncname=[&](parameter){Aktionen;}
Die "Aktionen" können wie in C auf die Variablen der umgebenden Funktion
lesend und verändernd zugreifen, das macht das "&" in den eckigen
Klammern, der sogenannte "Capture-Ausdruck".
Man kann dem Compiler bei der Optimierung enorm helfen, wenn man in die
eckigen Klammern keine oder nur die Variablen aufführt, die tatsächlich
von "Aktionen" benötigt werden, mit "=" als Kopie und mit "&" als
Referenz.
Solange "funcname" nur innerhalb der umgebenden Funktion aufgerufen
wird, bleibt alles halbwegs übersichtlich. Kompliziert wird es, wenn
"funcname" aufgerufen werden soll, wenn die umgebende Funktion beendet
wurde! Dann muss gcc den Capture in ein Funktionsobjekt "einfrieren" und
auf den Heap kopieren. Das erfordert new bzw. malloc() und eine
entsprechend betriebsbereite Heap-Konfiguration, für 8-Bit-Controller
Irrsinn.
Dies passiert für den Compiler in dem Moment, wenn man "funcname"
irgendwohin zuweist oder per return zurückgibt.
Der Klassiker hierfür sind solche Funktionen wie qsort(), denen man
gerne ein Lambda als Sortierkriterium übergeben möchte. Da der
C++-Compiler "weiß", dass qsort() den Zeiger nur als Callback aufruft
(und währenddessen der Stackframe gültig bleibt) wird in diesem Fall
(als eine Lösungsmöglichkeit) auf dem Stack ein Trampolin (ein Stück
Code zum Liefern der Stapelrahmen-Adresse) gelegt und diese Adresse dem
qsort() übergeben. Das geht so nicht Harvard-Architektur: Beim AVR geht
das bspw. mit einer Lambda-Version von qsort() welches ein
Funktionsobjekt statt einem Funktionszeiger erwartet. Dieses kann auf
dem Stack liegen.
Nur für den Fall eines leeren Capture:
1
autofuncname=[](parameter){Aktionen;}
braucht der Compiler keine Bocksprünge machen, und "funcname" ist ein
ganz normaler Funktionszeiger ohne versteckten Stackframe-Zeiger.
Es ist verboten, den Zeiger einer verschachtelten C-Funktion nach außen
zu geben, und wenn man es trotzdem irgendwie tut, wird beim Aufruf ein
heilloses Chaos ausbrechen, wenn darin auf Werte der (nicht mehr
laufenden) Umgebungsfunktion zugegriffen wird. Das war auch bei Turbo
Pascal so.