Lambda in C++

OP #6805815
Lesenswert?

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
1
return waitFor([&]() { return !(*MSTPSR[n] & bit);}, 100);
MSTPSR ist ein Register Array.

Meine Frage ist, wozu hier überhaupt ein Lambda? Welche Vorteile bietet 
es sich?
1
template <typename predicate>
2
bool waitFor(predicate check, uint32_t milliTimeout)

würde der Funktionsaufruf
1
return waitFor((*MSTPSR[n] & bit)!=0, 100);
nicht genau das selbe machen?
Welche vorteile bietet hier das Lambda
Gast #6806475
Lesenswert?

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.
#6806578
Lesenswert?

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.
Gast #6807089
Lesenswert?

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.
#6807126
Lesenswert?

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?
#6807204
Lesenswert?

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.
Gast #6807262
Lesenswert?

> 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.
Gast #6807576
Lesenswert?

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 😱
#6807722
Lesenswert?

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?
(Firma: TU Chemnitz) #7314056
Lesenswert?

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
    auto funcname = [&](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
    auto funcname = [](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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren