Forum: PC-Programmierung Lambda in C++


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Chandler B. (chandler)


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

von Sebastian V. (sebi_s)


Lesenswert?

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.

von Der Opa aus der Muppet Show (Gast)


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.

von Klaus W. (mfgkw)


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.

: Bearbeitet durch User
von Hannes (Gast)


Lesenswert?

Misra? Und da darf man per & capturen wenn das Lambda im 
return-Statement steht? Da sieht niemand ein Problem?

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Der Opa aus der Muppet Show (Gast)


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.

von Rolf M. (rmagnus)


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?

von Klaus W. (mfgkw)


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.

: Bearbeitet durch User
von Der Opa aus der Muppet Show (Gast)


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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von MaWin (Gast)


Lesenswert?

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.

von IchHabsVerstanden (Gast)


Lesenswert?

Das sollte weiter helfen:
https://youtu.be/WMT5XzXFhUs

von Hannes (Gast)


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 😱

von Rolf M. (rmagnus)


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?

von Henrik H. (Firma: TU Chemnitz) (heha)


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

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.