Hallo zusammen, ich untersuche gerade verschiedene Designpatterns für
die Nutzung von C++ in der Embedded Software Entwicklung (ohne OS).
Vorallem das Thema statische Polymorphie per CRTP
(https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)
klingt sehr interessant, allerdings habe ich hierzu folgende
Frage/Problem:
Bei CRTP ist das Basis-Interface keine Klasse mit virtuellen Methoden,
sondern ein Template, das als Templateparameter die abgeleitete
Implementierungs-Klasse selbst erhält, sodass die Basis ihre Ableitungen
kennt. An sich ein sehr kleveres Desing, allerdings bedeutet das doch
auch, dass andere Klassen/Funktionen, die das Interface verwenden wollen
(als Parameter erwarten) ebenfalls Templates sein müssen, oder?
break;/* just for demonstration, usually infinity loop... */
106
}
107
return0;
108
}
Hier zum Rumspielen: http://cpp.sh/8zgc3
Das Beispiel zeigt eine RgbLed-Klasse, die drei einfache digitale
Ausgänge erwartet, an welchen farbige LEDs (Rot, Grün, Blau)
angeschlossen sein sollten. Die Klasse bietet dann eine
setColor()-Funktion, die die Farbmisching übernimmt. Hierzu bezieht sich
die Klasse auf DigitalOut, als generisches Interface. DigitalOutStm32
stellt dann die konkrete, plattformspezifische Interface-Implementierung
für die STM32 HAL da.
Wie man sieht erfordert die CRTP Variante, dass nicht nur DigitalOut ein
Template ist, sondern auch RgbLed und theoretisch auch alles was RgbLed
verwenden möchte (die Template-Argumentliste wird potenziell riesig!)
...
Mir gefällt die Möglichkeit auf virtual verzichten zu können, aber ich
möchte nicht alle Treiber und Bibliotheken als Templates verfassen
müssen.
Hat jemand eine Idee? Habe ich CRTP falsch verstanden? Hat jemand
Erfahrung mit diesem Designpattern?
Danke vorab!
Christian K. schrieb:> allerdings bedeutet das doch auch, dass andere Klassen/Funktionen, die> das Interface verwenden wollen (als Parameter erwarten) ebenfalls> Templates sein müssen, oder?
Ja, natürlich, es sei denn sie brauchen nur eine bestimmte Instanzierung
des Templates. Bei statischer Verknüpfung zwischen interface und dessen
Nutzer müssen halt alle Nutzer es statisch kennen und daher ggf.
Templates werden. Das ist der Preis wenn man kein "virtual" will...
Ich staune immer wieder, welchen riesen Wust an Code man in C++ für die
allerkleinsten Aufgaben erzeugen kann.
Ich bin vermutlich schon zu alt für C++.
Ich komme mir beim Lesen immer vor, wie Buchbinder Wanninger. Eine
Instanz zeigt auf die nächste, bis man gar nicht mehr weiß, worum es
überhaupt geht.
Peter D. schrieb:> Ich staune immer wieder, welchen riesen Wust an Code man> in C++ für die allerkleinsten Aufgaben erzeugen kann.
Hauptziel von C++ ist Abstraktion. Ein korrekt
funktionierendes LESBARES Programm ist eine billigend
in Kauf genommene Nebensache.
Peter D. schrieb:> riesen Wust an Code man in C++ für die> allerkleinsten Aufgaben
Ein Problem, das so komplex ist, dass abstraktere Programmiertechniken
am Ende einfacher (und weniger fehleranfällig sind) sind, eignet sich
schlecht für ein Mini-Beispiel, das zum Verstehen und im Forum fragen
geeignet ist.
Die Diskusion läuft hier immer ungefähr so ab:
A: "Ich habe Technik XY entdeckt, mit der man nach 2h Einarbeitung
riesige Probleme flexibel und fehlersicher bearbeiten kann. Hier ein
Miniatur-Beispiel anhand eines LED-Blinkers, wie genau könnte man das am
besten auf das Feature UVW erweitern?"
B: "Du idiot, led blinker sind in retro-C viel einfacher LOL."
C: "Das ist alles viel zu langsam, in Assembler läuft die Blinkschleife
mit 2 Takten weniger."
Gerade so etwas wie CRTP geht in C nicht - es ist effizient und
flexibel, in C muss man sich zwischen einem von beiden entscheiden.
Indem man als CRTP-Parameter eine Klasse mit virtuellen Funktionen
übergibt kann man die Runtime-Polymorphie sogar nachträglich nachrüsten,
und hat somit alle Freiheiten. Super lesbar ist es nicht, aber je nach
Situation (wenn man die Flexibilität behalten will) sogar effizienter
als C. Wenn Lesbarkeit an 1. Stelle steht nimmt man Python oder Java,
aber bestimmt nicht C.
Peter D. schrieb:> Ich bin vermutlich schon zu alt für C++.
C++ gibt es seit über 30 Jahren. Du hattest genug Gelegenheit es zu
lernen. Wenn du zu faul/kurzsichtig dafür warst, bist du selber schuld,
und es gibt dir keine Rechtfertigung sinnlos darüber zu lästern.
Die Peanut-Gallery antwortet:
Dr. Sommer schrieb:> Wenn du zu faul/kurzsichtig dafür warst, bist du selber schuld,> und es gibt dir keine Rechtfertigung sinnlos darüber zu lästern.
Und C++98 und C++17 sind sich auch so unsagbar ähnlich, besonders mit
all den neuen Features für embedded. Wer das in den 80ern gelernt hat,
ist voll auf dem neuesten Stand!
Ok, dachte schon ich bin im falschen Forumsbereich... danke an alle für
die Antworten!
Tom schrieb:> Ein Problem, das so komplex ist, dass abstraktere Programmiertechniken> am Ende einfacher (und weniger fehleranfällig sind) sind, eignet sich> schlecht für ein Mini-Beispiel, das zum Verstehen und im Forum fragen> geeignet ist.
Genau das! Vielleicht hätte ich auch den Code nicht mit Compiler-Switch
verfassen sollen. Dachte das hebt die Unterschiede hervor...
Dr. Sommer schrieb:> Wenn du zu faul/kurzsichtig dafür warst, bist du selber schuld,> und es gibt dir keine Rechtfertigung sinnlos darüber zu lästern.
Bitte sachlich bleiben. Oftmals ist es nicht mal die Entscheidung des
Entwicklers. Ich arbeite gerade selbst daran (daher die Untersuchung...)
Vorurteile gegenüber C++ abzubauen, damit es bei uns eingesetzt werden
kann.
Das Beispiel ist tatsächlich nicht mal so weit hergeholt. Für eine
plattformübergreifende Entwicklung (heute STM32, morgen PIC, ...) kann
selbst bei einfachen digital IOs eine polymorphe API sinnvoll sein um
abstraktere Treiber/Funktionen/Bibliotheken zu entwickeln. Der Overhead
darf dabei allerdings nicht zu groß werden (wobei man den bei einer
gleichwertigen C-Lösung auch hätte!)
Dr. Sommer schrieb:> Indem man als CRTP-Parameter eine Klasse mit virtuellen Funktionen> übergibt kann man die Runtime-Polymorphie sogar nachträglich nachrüsten,> und hat somit alle Freiheiten.
Wie meinst du das genau? Unter dem CRTP-Parameter verstehe ich die
abgeleitete Klasse, die ich dem Basis-Template zur Spezialisierung beim
Erben übergebe (in meinem Beispiel DigitalOutStm32). Das ändert dann
aber nichts an der Tatsache, dass das Interface ein Template ist, sodass
der Benutzer (hier: RgbLed) auch eins sein muss. Oder verstehe ich dich
falsch?
Und täglich grüsst das Murmeltier...
Diese Threads zum Thema C++ und Embedded tauchen immer wieder auf und
bleiben letztendlich meist von akademischer Natur. Im produktiven
Einsatz bringt meistens der vermutete Vorteil von C++ wenig, bzw. kann
man oft beobachten, wie sich eine Menge Programmierer damit verzetteln
und die Probleme dann unbegrenzt kaskadieren, wenn der
template-generierte Code debuggt werden muss. Da hat sich in 30 Jahren
C++ nicht viel geändert. Der Trugschluss ist, dass der Template-Wust ein
gut designtes 'OS' (oder Low-Level Kernel-Framework) nicht ersetzt.
Low-Level-Kram wie Register, gemultiplexte GPIOs usw. sind von der
Komplexität noch so gering, dass man sie nicht mit C++
kaputtabstrahieren sollte. Der so generierte Code ist seltenst
minimal-simpel, und wenn's um Safety und Beweisbarkeit geht, fliegen
Templates schon mal raus. Hat schon seine Gründe, dass die meisten OS
noch in strikt 'retro'-C geschrieben sind und sich da so bald auch nix
dran ändert.
Bei komplexeren Themen im User-Space wie GUIs oder Objekt-Pools wo es
egal ist, wieviel Code nun genau generiert wird, haben Templates schon
ihre Berechtigung und man spart u.U. viel Zeit. Bei Low-Level embedded
ist leider (aus Erfahrung) die Portabilität von C++-basierten Lösungen
meist für die Katz und die wunderschöne Theorie bleibt dann eben
akademische Spielerei.
Christian K. schrieb:> Das Beispiel ist tatsächlich nicht mal so weit hergeholt. Für eine> plattformübergreifende Entwicklung (heute STM32, morgen PIC, ...) kann> selbst bei einfachen digital IOs eine polymorphe API sinnvoll sein um> abstraktere Treiber/Funktionen/Bibliotheken zu entwickeln.
Wo ist eigentlich der große Vorteil gegenüber einem
plattformspezifischen C-Header, der für GPIOs einfache "static
inline"-Funktionen deklariert? Eine handvoll Taktzyklen?
S. R. schrieb:> Christian K. schrieb:>> Das Beispiel ist tatsächlich nicht mal so weit hergeholt. Für eine>> plattformübergreifende Entwicklung (heute STM32, morgen PIC, ...) kann>> selbst bei einfachen digital IOs eine polymorphe API sinnvoll sein um>> abstraktere Treiber/Funktionen/Bibliotheken zu entwickeln.>> Wo ist eigentlich der große Vorteil gegenüber einem> plattformspezifischen C-Header, der für GPIOs einfache "static> inline"-Funktionen deklariert? Eine handvoll Taktzyklen?
Mann könnte verschiedene GPIO Implementierungen für jede Farbe der RGB
LED verwenden. Z.B. einen direkt am STM, einen an einem per SPI
angebundenen Schieberegister und einen der per WLAN angesprochen wird.
Ob solche Ansätze so weit unten (in der Treiberschicht) wirklich
notwendig sind und auch was bringen? Kann ich mir gerade kein Urteil
drüber bilden. Kann was bringen aber ob es den Aufwand gegenüber
klassischer Polymorphie wert ist? Auf kleinen Systemen (uC mit schmalen
RTOS oder Bard metal) hat man meist die Notwendigkeit nicht und auf
größeren (uP mit richtigem OS) tut die zusätzliche Indirektion einer
virtuellen Methode nicht weh. Aber grundsätzlich finde ich die
Möglichkeiten die C++ bietet interessant und will sie im uC Bereich auch
Mal evaluieren.
Matthias
Christian K. schrieb:> Wie man sieht erfordert die CRTP Variante, dass nicht nur DigitalOut ein> Template ist, sondern auch RgbLed und theoretisch auch alles was RgbLed> verwenden möchte
im Prinzip ja (s.u.)
> Mir gefällt die Möglichkeit auf virtual verzichten zu können, aber ich> möchte nicht alle Treiber und Bibliotheken als Templates verfassen> müssen.
Warum nicht. Das hat auch Vorteile.
> Hat jemand eine Idee? Habe ich CRTP falsch verstanden? Hat jemand> Erfahrung mit diesem Designpattern?
CRTP hat im wesentlichen zwei getrennte Einsatzgebiete:
1) Mixin (from above)
2) stat. Polymorphie "Helper"
zu 1)
Hier wird die Funktionalität des Subtyps durch den Basistyp einfach
erweitert, wobei der Basistyp auf die Elemente des Subtyps zugreift.
M.E. sollte man dazu aber non-member non-friends vorziehen. Vererbung
wegen Code-Reuse ist immer einer schlechte Idee. So auch hier, denn es
gilt hier nicht die ist-ein Beziehung zwischen Sub- und Basis-Typ.
zu 2)
Wie Du schon bemerkt hast, müssen Clienten, die die stat. Polymorphie
nutzen wollen, natürlich templates sein. In C++ bedeutet stat.
Polymorphie nun einmal nichts anderes als generischer Code via
templates.
Klienten stellen an die verwendeten template-Typ-Parameter implizite
Typanfordernungen, d.h. man fordert, dass von T eine bestimmte Operation
unterstützt wird. Ist das nicht der Fall, bricht die Kompilation ab.
Diese Anforderung kann via CRTP explizit gemacht werden. Auch hier:
verwende ich im CRTP einen Subtyp, der die (impliziten) Anforderungen
nicht erfüllt, kompiliert es nicht. Der Fehler taucht nur an anderer
Stelle auf.
Letztendlich geht es darum, Typanforderungen an template-Typ-Parameter
irgendwie explizit zu machen.
Aber genau dazu sind Concepts erfunden worden: mit ihnen kann ich
Anforderungen an Typ-Parameter explizit formulieren. Sind diese
Anforderungen nicht erfüllt, so bricht natürlich auch die Kompilation
ab, aber nun an der richtigen Stelle und mit viel besseren
Fehlermeldungen.
(BTW: die Idee zu concepts ist (fast) so alt wie die template-Mechanik
selbst, wurden aber erst sehr spät als Entwurf akzeptiert als
concepts-ts und befinden sich nun auf dem Weg in C++20.)
Zudem kann ich mit concepts, die sich in ihren requirements nicht
überdecken, Klassentemplates elegant partiell spezialisieren, was dann
wie ein "overload" von Klassen(!) aussieht.
concepts sind als concepts-ts im gcc seit 6.3 mit drin und werden
offiziell in C++20 enthalten sein.
Für mich bedeutet das als Konsequenz:
zu 1) mixin from above: war m.E. noch nie eine sooo tolle Idee.
zu 2) stat. Polymorphie "Helper": nur eine "historische" Krücke, deren
Einsatz m.E. überschätz wurde. Die richtige Lösung heisst concepts.
Zu Deinem use-case: mach in Deinem RgbLed-template die Outputs einfach
zu template-Paramteren (btw.: da sollte doch ein Parameter reichen). Du
brauchst da kein CRTP (wie gesagt: CRTP so eingesetzt verschiebt nur den
Fehlerort).
Christian K. schrieb:> Genau das! Vielleicht hätte ich auch den Code nicht mit Compiler-Switch> verfassen sollen. Dachte das hebt die Unterschiede hervor...
Das ist mir etwas später aufgefallen, daß der Code quasi doppelt ist.
Interessant finde ich aber das eigentliche Problem, wie man IO-Zugriffe
intelligent kapseln und skalieren könnte. Dafür konnte ich jedoch keine
Lösung darin erkennen.
Wenn man z.B. 20 LEDs damit steuern will, kann es ja wohl nicht sein,
daß man 20 verschiedene setColor() schreiben muß. Auch sollte die Lösung
so flexibel sein, daß sie nicht nur für direkte IO-Pins geht, sondern
auch für virtuelle, die z.B. über einen MAX7219 ausgegeben werden.
In plain C würde ich dafür ein Array mit Pinnummern erstellen, was die
Zuordnung der LEDs vornimmt. Entweder mit 60 Einträgen für jede LED oder
mit 20 Einträgen, wenn alle RGB-LEDs auf je 3 aufeinanderfolgende Pins
verdrahtet werden.
Peter D. schrieb:> Wenn man z.B. 20 LEDs damit steuern will, kann es ja wohl nicht sein,> daß man 20 verschiedene setColor() schreiben muß.
Nein, wieso kommst Du darauf?
> Auch sollte die Lösung> so flexibel sein, daß sie nicht nur für direkte IO-Pins geht, sondern> auch für virtuelle, die z.B. über einen MAX7219 ausgegeben werden.
Kann man machen.
> In plain C würde ich dafür ein Array mit Pinnummern erstellen, was die> Zuordnung der LEDs vornimmt. Entweder mit 60 Einträgen für jede LED oder> mit 20 Einträgen, wenn alle RGB-LEDs auf je 3 aufeinanderfolgende Pins> verdrahtet werden.
In C++ würde man eher ein variadisches Template nehmen. Einmal mit dem
Treiber für den z.B. MAX xxxx und einmal für die WS2812 oder was auch
immer als Template-Typ-Parameter. Die Treiber selbst sind natürlich auch
wieder templates mit den Pins als Parameter und was sonst noch so
gebraucht wird. Die Pins sind auch wieder templates, mit den Ports und
Pin-Nummern als Parameter, etc. ...
Geht alles ganz gut und ist 0-overhead. Mit dem Vorteil, dass man zur
Compilezeit aus so etwas wie einen Konfigchecker machen kann. Ist
beispielsweise bei den PinMuxern (SAM, XMega, STM) ganz praktisch, weil
nicht alle internen Pads der internen Peripherie auf beliebige Pins
gelegt werden kann. So vermeidet man viele Laufzeitfehler.
Peter D. schrieb:> Wenn man z.B. 20 LEDs damit steuern will, kann es ja wohl nicht sein,> daß man 20 verschiedene setColor() schreiben muß. Auch sollte die Lösung> so flexibel sein, daß sie nicht nur für direkte IO-Pins geht, sondern> auch für virtuelle, die z.B. über einen MAX7219 ausgegeben werden.> In plain C würde ich dafür ein Array mit Pinnummern erstellen, was die> Zuordnung der LEDs vornimmt. Entweder mit 60 Einträgen für jede LED oder> mit 20 Einträgen, wenn alle RGB-LEDs auf je 3 aufeinanderfolgende Pins> verdrahtet werden.
Muss er ja auch nicht? 20 Instanzen der Klasse tuns auch...?
Und natürlich ginge das mit virtuellen Pins auch. setHigh darf ja ruhig
was anderes tun als nur a Register zu schreiben...
Vincent H. schrieb:> Muss er ja auch nicht? 20 Instanzen der Klasse tuns auch...?
Das meinte ich doch, man hat den 20-fachen Schreibaufwand und den
20-fachen Code.
Ich würde es gerne auf einen Code, d.h. eine Funktion reduzieren, die
die unterschiedlichen Bitnummern aus einer Tabelle entnimmt. Oder wenn
man die LEDs entsprechend verdrahtet, die Bitnummern aus einer
Vorschrift errechnet.
Peter D. schrieb:> Vincent H. schrieb:>> Muss er ja auch nicht? 20 Instanzen der Klasse tuns auch...?>> Das meinte ich doch, man hat den 20-fachen Schreibaufwand und den> 20-fachen Code.> Ich würde es gerne auf einen Code, d.h. eine Funktion reduzieren, die> die unterschiedlichen Bitnummern aus einer Tabelle entnimmt. Oder wenn> man die LEDs entsprechend verdrahtet, die Bitnummern aus einer> Vorschrift errechnet.
Die wichtige Frage wäre: wann?
Zur Laufzeit, das wäre die C-Variante (auch Arduino macht das so)
Oder zur Compile-Zeit, mit dem Ergebnis "SBI 0x19,1".
Hängt also davon ab, wo man knappe Resourcen hat.
Peter D. schrieb:> Vincent H. schrieb:>> Muss er ja auch nicht? 20 Instanzen der Klasse tuns auch...?
Es brauchen ja auch gar keine Objekte zu sein. Template-Instanzen tuns
ja auch. Denn die Konfig ist ja eher statisch.
> Ich würde es gerne auf einen Code, d.h. eine Funktion reduzieren, die> die unterschiedlichen Bitnummern aus einer Tabelle entnimmt. Oder wenn> man die LEDs entsprechend verdrahtet, die Bitnummern aus einer> Vorschrift errechnet.
Ja, kann man doch. Statt einer Tabelle kann man auch einfach eine
Typ-Liste der Pins o.ä. nehmen:
Peter D. schrieb:> Ich würde es gerne auf einen Code, d.h. eine Funktion> reduzieren, die die unterschiedlichen Bitnummern aus> einer Tabelle entnimmt. Oder wenn man die LEDs> entsprechend verdrahtet, die Bitnummern aus einer> Vorschrift errechnet.
Dazu würde ich Anleihen aus der SPS-Technik aufnehmen:
Dein Bitfeld im RAM bildet das "Prozessabbild der
Ausgänge", das den gewünschten Zustand der Leuchtdioden
aufnimmt. Timergesteuert werden die Treiber aufgerufen,
die die relevanten Bits aus dem Prozessabbild der Ausgänge
zusammenklauben und auf die reale Hardware schreiben.
(Dafür lassen sich sicher auch wiederverwendbare Teil-
funktionen schaffen, beispielsweise das effiziente
Bestimmen vom Bitpermutationen, das Serialisieren o.ä.)
Dadurch ist die Anwendungslogik des Programmes komplett
von den Details der Hardware-Ansteuerung entkoppelt;
letztere stecken nur in den Funktionen, die die "Hardware-
treiber" bilden.
Ob ein Laufzeitnachteil entsteht, wäre zu untersuchen,
denn es besteht weder im "Anwendungsprogramm" noch in
den "Treibern" ein Zwang, bitseriell zu arbeiten (außer
natürlich, der Treiber muss tatsächlich "von Hand"
serialisieren).
Wilhelm M. schrieb:> Ja, kann man doch. Statt einer Tabelle kann man auch einfach eine> Typ-Liste der Pins o.ä. nehmen:>> led1 = Pin<PortA, 1>;> led2 = Pin<PortB, 2>;> led3 = Pin<PortC, 3>;
Was habe ich denn damit gewonnen?
Bei 20 RGB-LEDs müßte diese Typ-Liste ja bis led60 gehen. Und dazu noch
die 20 Funktionen zum Setzen.
Ich nehme da nur eine Funktion:
Peter D. schrieb:> Wilhelm M. schrieb:>> Ja, kann man doch. Statt einer Tabelle kann man auch einfach eine>> Typ-Liste der Pins o.ä. nehmen:>>>> led1 = Pin<PortA, 1>;>> led2 = Pin<PortB, 2>;>> led3 = Pin<PortC, 3>;>> Was habe ich denn damit gewonnen?> Bei 20 RGB-LEDs müßte diese Typ-Liste ja bis led60 gehen. Und dazu noch> die 20 Funktionen zum Setzen.> Ich nehme da nur eine Funktion:>
1
boolsetColor(uint8_tled_no,uint8_tcolor);
Und wo bitte hast Du jetzt Deine Pins der Leds definiert? Irgendwo wirst
Du ja irgendetwas definieren müssen. Zeig doch mal, wie Du die led_no
auf eine Port/Pin-Kombination abbildest.
Die Problematik besteht darin, dass man die Zusammenhänge und die
Vorteile erst versteht, wenn man sich einen einigermassen umfassenden
Überblick über C++ und <templates> verschafft hat.
C++ Bücher sind für Praktiker von der elektrotechnischen Seite her oft
schwer nachzuvollziehen, weil zu abstrakt und zu Informatik lastig
ausformuliert.
Beispiele (compilierbar) wären hilfreich, sind eher selten zu finden.
Oben angefügt ein Code-Snippet zur Illustration, das zeigen soll, wie
C++ eventuell doch gewisse positive Eigenschaften hätte.
Alexandra
MitLeserin schrieb:> Beispiele (compilierbar) wären hilfreich, sind eher selten zu finden.
Bin ja voll auf Arduino....
Beispiele satt..
Aber Pin Gedöns im OOP Mäntelchen, eher selten.
(falls die Libraries gesehen werden wollen, kann ich sie gerne
nachliefern)
Wilhelm M. schrieb:> Und wo bitte hast Du jetzt Deine Pins der Leds definiert?
Wie schon gesagt, als Array (Portadresse, Bitmaske).
Oder als Vorschrift, z.B. alle LEDs liegen hintereinander als 3 Bit je
RGB im Memory.
Hier mal als Vorschrift:
MitLeserin schrieb:> Oben angefügt ein Code-Snippet zur Illustration, das zeigen soll, wie> C++ eventuell doch gewisse positive Eigenschaften hätte.
Das werde ich mir mal näher ansehen.
Naja, wo wird denn hier max7219_data initialisiert? Was bedeuten Die
Daten in dem Array? In meinen Augen völlig undurchsichtig. Die gesamte
Konfig ist also irgendwo im Code versteckt ...
Peter D. schrieb:> MitLeserin schrieb:>> Oben angefügt ein Code-Snippet zur Illustration, das zeigen soll, wie>> C++ eventuell doch gewisse positive Eigenschaften hätte.>> Das werde ich mir mal näher ansehen.
Ähnliches habe ich hier auch schon ein paarmal gepostet. Nur leider
schauen sich die Leute das erstens nicht richtig an bzw. verstehen
nichts über templates bzw. TMP und beschweren sich dann über die
Unlesbarbeit. Viele wissen auch nicht, was Metafunktionen sind oder wozu
man Typabbildungen braucht oder wie man zur Compilezeit mit Typen (nicht
mit Werten) rechnen kann. Und dann wird sich beschwert, dass es
kompliziert sei. Dazu gehört eben auch, dass man sich damit von Grund
auf einmal beschäftigt. Das template-System ist Turing-vollständig, aber
eben eine eigene Sprache in der Sprache. Ich kann nur sagen, es lohnt
sich sehr, damit auseinander zu setzen.
Wilhelm M. schrieb:> Ähnliches habe ich hier auch schon ein paarmal gepostet.
Nur ist das aber mal ein praktisches Beispiel, wo auch der ungeübte
einen roten Faden erkennen kann.
Deine Beispiele kommen immer hoch theoretisch daher, d.h. man muß C++
schon perfekt können, um sie zu verstehen.
Peter D. schrieb:> Wilhelm M. schrieb:>> Ähnliches habe ich hier auch schon ein paarmal gepostet.>> Nur ist das aber mal ein praktisches Beispiel, wo auch der ungeübte> einen roten Faden erkennen kann.> Deine Beispiele kommen immer hoch theoretisch daher, d.h. man muß C++> schon perfekt können, um sie zu verstehen.
Wobei man durch McuCpp leicht erschreckt werden kann, denn es basiert
auf C++03, wo man vieles was in C++17 als einfacher Code daherkommt,
kompliziert als Meta-Programm schreiben muß. Ich nenne nur mal
"parameter packs" als Sprachkonstrukt vs "loki"-Template-Halden.
Von dem, was der Benutzer der Bibliothek hinschreibt, ist das aber fast
auf aktuellem Stand.
Der wesentliche Unterschied zwischen der "PinNummer -> Addressen/Maske"
Variante (z.B. Arduino) und McuCpp mit Einem Typ, der mehr oder weniger
die selbe Information beinhaltet, ist daß im letzten Fall der Compiler
diese Info verarbeitet und nicht nur eine Laufzeitroutine, die halt
nicht mal per JIT an die konkrete Situation angepaßten Objektcode
erzeugt.
Wenn z.B. auf einem Chip die virtuellen Pins 1,2,3 in einem Port
aufsteigend hintereinander liegen, dann entsteht kompakterer Code, wenn
diese Wild auf Ports verteilt sind, dann wird's komplizierter, die
Arduino-Variant "zur Laufzeit" ergibt immer universell funktionierenden
Code.
Nebenbei kann man mit dem Typ Pin<PortA,2> nur machen, was man dafür
vorgesehen hat. Hilft gegen das gelegtlich zu sehende setzen eines Bits,
das es in dem angesprochenen Port gar nicht gibt, es aber nur ein Macro
ist, das eine INT-Konstante liefert und der Port nur ein Zeiger auf eine
8-Bit-Speicherstelle. (typische Frage: warum geht der Timer
nicht/falsch? Antwort: Eventuell weil TCCR2A kein Bit CS01 hat?!)
Peter D. schrieb:> Wilhelm M. schrieb:>> Ähnliches habe ich hier auch schon ein paarmal gepostet.>> Nur ist das aber mal ein praktisches Beispiel, wo auch der ungeübte> einen roten Faden erkennen kann.> Deine Beispiele kommen immer hoch theoretisch daher, d.h. man muß C++> schon perfekt können, um sie zu verstehen.
Stimmt nicht so ganz: genau so ein Beispiel hatte ich schon mehrfach
gepostet. Ist vllt an Dir vorbeigegangen ...
Der theoretische Eindruck entsteht vielleicht, weil ich versuche, auch
immer die richtigen Begriffe zu verwenden. Z.B. spreche ich auch vom
Instanziieren eines Templates, was etwas anderes ist, als das
Instanziieren eines Typs (vulgo Klasse), spreche von
template-typ-parametern, template-template-parametern oder
template-nicht-typ-parametern. Das sind einfach die offiziellen
Begriffe. Da aber sprache Ausdruck der Gedanken sind, finde ich es
wichtig, diese Begriffe zu verwenden. Auch der Begriff Meta-Funktion ist
ein Begriff dieser Kategorie. Und ich glaube kaum, dass dieser Begriff
allen hier bekannt ist. Man könnte ihn aber auch mal nachlesen ...
Wilhelm M. schrieb:> Der theoretische Eindruck entsteht vielleicht, weil ich versuche, auch> immer die richtigen Begriffe zu verwenden...> Da aber sprache Ausdruck der Gedanken sind, finde ich es> wichtig, diese Begriffe zu verwenden
Das ist löblich und auch gut so. Bloss wenn der Leser das KnowHow noch
nicht hat, weiss er nicht was der Verfasser meint und kann die Gedanken
nicht nachvollziehen. Das Vorurteil gegen C++ bleibt bestehen.
Carl D. schrieb:> Wobei man durch McuCpp leicht erschreckt werden kann, denn es basiert> auf C++03, wo man vieles was in C++17 als einfacher Code daherkommt,> kompliziert als Meta-Programm schreiben muß. Ich nenne nur mal> "parameter packs" als Sprachkonstrukt vs "loki"-Template-Halden.> Von dem, was der Benutzer der Bibliothek hinschreibt, ist das aber fast> auf aktuellem Stand.
Siehe oben:
Bitte an compilierbaren Beispielen detaillierter erklären. Wenn möglich
thematisch separiert, jeweils ein c++03 snippet vs. aequivalentes c++17
snippet.
Ich würde mich freuen..
MitLeserin schrieb:> Wilhelm M. schrieb:>> Der theoretische Eindruck entsteht vielleicht, weil ich versuche, auch>> immer die richtigen Begriffe zu verwenden...>> Da aber sprache Ausdruck der Gedanken sind, finde ich es>> wichtig, diese Begriffe zu verwenden>> Das ist löblich und auch gut so. Bloss wenn der Leser das KnowHow noch> nicht hat, weiss er nicht was der Verfasser meint und kann die Gedanken> nicht nachvollziehen. Das Vorurteil gegen C++ bleibt bestehen.
Wir leben im Google-Zeitalter: wenn ich nicht weiß, dass Meta-Funktionen
sind, dann schaue ich halt nach. Das ist eine Holschuld: ich muss das
keinem hier beibringen.
Das Vorurteil wird gerne gefüttert - und zwar solange, wie jemand nicht
bereit ist, sich auf neue Dinge einzulassen. Leider passiert das hier
aber regelmäßig, es werden nur die altbekannten Statements ausgetauscht.
Und darüber staune ich, sollte es doch in einem technologiegeprägtem
Metier eigentlich anders zugehen.
> Carl D. schrieb:>> Wobei man durch McuCpp leicht erschreckt werden kann, denn es basiert>> auf C++03, wo man vieles was in C++17 als einfacher Code daherkommt,>> kompliziert als Meta-Programm schreiben muß. Ich nenne nur mal>> "parameter packs" als Sprachkonstrukt vs "loki"-Template-Halden.>> Von dem, was der Benutzer der Bibliothek hinschreibt, ist das aber fast>> auf aktuellem Stand.>> Siehe oben:> Bitte an compilierbaren Beispielen detaillierter erklären. Wenn möglich> thematisch separiert, jeweils ein c++03 snippet vs. aequivalentes c++17> snippet.
Noch Wünsche bitte?
Vincent H. schrieb:> Der Spaß geht ohne variadic templates und damit C++11 nicht.
Doch, das hatte ich ja geschrieben, es geht mit Hilfe der
Meta-Programming-Lib "loki" von Andrei Allexandrescu ab C++03 (2003 ist
das Entstehungsjahr von "loki"), aber wäre in C++17 einfacher zu machen
und besser zu verstehen.
Carl D. schrieb:> Vincent H. schrieb:>> Der Spaß geht ohne variadic templates und damit C++11 nicht.>> Doch, das hatte ich ja geschrieben, es geht mit Hilfe der> Meta-Programming-Lib "loki" von Andrei Allexandrescu ab C++03 (2003 ist> das Entstehungsjahr von "loki"), aber wäre in C++17 einfacher zu machen> und besser zu verstehen.
Ja boost::mpl oder so emuliert ja auch variadic Verhalten... aber na
danke, sicher nicht freiwillig. =)
Ich denke meine ursprüngliche Frage ist beantwortet, ich fasse für mich
zusammen: Ja, CRTP bedeutet, dass der Nutzer eines Interfaces ebenfalls
ein Template sein muss, siehe
Dr. Sommer schrieb:> Bei statischer Verknüpfung zwischen interface und dessen> Nutzer müssen halt alle Nutzer es statisch kennen und daher ggf.> Templates werden. Das ist der Preis wenn man kein "virtual" will...
Praktikable Techniken um diesen Umstand zu umgehen habe ich bisher nicht
gelesen, sodass ich akzeptiere, dass es sie vermutlich nicht gibt.
Wilhelm M. schrieb:> zu 1) mixin from above: war m.E. noch nie eine sooo tolle Idee.>> zu 2) stat. Polymorphie "Helper": nur eine "historische" Krücke, deren> Einsatz m.E. überschätz wurde. Die richtige Lösung heisst concepts.
Diese Einschätzung erscheint mir logisch. Ich denke, dass CRTP eine
interessante Technik ist, die man kenne sollte. Für meine Anforderungen
an ein polymorphes Interface scheint sie aber ungeeignet zu sein.
Zu der aufgekommenen Diskussion über Sinn- und Unsinn von C++ und
abstrakten Programmiertechniken in der Embedded Software:
Eine zusätzliche Einschränkung, die bisher noch nicht angesprochen
wurde, ist die Compiler-Verfügbarkeit. Der neuste Arm Compiler, Version
6, wirbt damit C++14 zu unterstützen. Ich arbeite immer noch viel mit
Version 5, die C++03 vollständig und C++11 teilweise beherrscht... von
C++17 oder gar C++20 ist die Embedded Software vermutlich noch weit
entfernt. Ich beschränke mich derzeit auf C++98/C++03 als kleinsten
gemeinsamen Nenner für alle Plattformen.
Variadic templates, Concepts, u.ä. stehen für mich daher leider nicht
zur Debatte. Ich suche nach einem Spagat zwischen moderner (mMn
effizienterer) Technik und konservativer Entwicklung, was scheinbar der
Suche nach dem heiligen Gral entspricht...
Carl D. schrieb:> Doch, das hatte ich ja geschrieben, es geht mit Hilfe der> Meta-Programming-Lib "loki" von Andrei Allexandrescu ab C++03
Das ist ein sehr interessanter Hinweis, mit der Bibliothek und seinem
Buch werde ich mich eingehender befassen, danke!
Christian K. schrieb:> Wilhelm M. schrieb:>> zu 1) mixin from above: war m.E. noch nie eine sooo tolle Idee.>>>> zu 2) stat. Polymorphie "Helper": nur eine "historische" Krücke, deren>> Einsatz m.E. überschätz wurde. Die richtige Lösung heisst concepts.>> Diese Einschätzung erscheint mir logisch. Ich denke, dass CRTP eine> interessante Technik ist, die man kenne sollte. Für meine Anforderungen> an ein polymorphes Interface scheint sie aber ungeeignet zu sein.
Denke ich auch: Du kannst Dein Problem ganz ohne CRTP lösen
(Konsequenzen: s.o.)
> Eine zusätzliche Einschränkung, die bisher noch nicht angesprochen> wurde, ist die Compiler-Verfügbarkeit. Der neuste Arm Compiler, Version> 6, wirbt damit C++14 zu unterstützen.
Welcher Compiler ist es denn konkret? Es gibt ja mehr als einen ...
> Variadic templates, Concepts, u.ä. stehen für mich daher leider nicht> zur Debatte. Ich suche nach einem Spagat zwischen moderner (mMn> effizienterer) Technik und konservativer Entwicklung, was scheinbar der> Suche nach dem heiligen Gral entspricht...
Dann schau Dir auf jeden Fall constexpr in allen seinen Ausprägungen an
(constexpr-functions, constexpr members, literal types, constexpr
lambdas).
> Carl D. schrieb:>> Doch, das hatte ich ja geschrieben, es geht mit Hilfe der>> Meta-Programming-Lib "loki" von Andrei Allexandrescu ab C++03>> Das ist ein sehr interessanter Hinweis, mit der Bibliothek und seinem> Buch werde ich mich eingehender befassen, danke!
Würde ich nicht machen. Ist definitiv Schnee von gestern. Investiere
Deine Zeit in Maßnahmen, wie Du Deine Code- und Tool-Basis modernisieren
kannst.
Christian K. schrieb:> Der neuste Arm Compiler, Version 6, wirbt damit C++14 zu unterstützen.
Ja, weil er auf Clang basiert... ;-) der freie GCC unterstützt auch
C++17 für ARM. Im Atollic Studio z.B. gibt es da auch eine nette IDE
für.
Wilhelm M. schrieb:> Welcher Compiler ist es denn konkret? Es gibt ja mehr als einen ...
Er meint den von ARM Keil Mdk mitgelieferten. Version 5 hieß armcc,
Version 6 armclang, der wie oben erwähnt auf clang basiert. Damit
funktionieren auch sehr viele Compiler flags vom gcc. Und er kann lto.
Und c++14 bzw sogar 17 als community Version, d.h. Noch nicht offiziell
unterstützt.
Ich hatte in unserer Firma dank Chipmangel ein sehr komplexes Projekt
von einer MCU auf die andere zu Portieren. Dank Abstraktion habe ich das
in einem Tag (samt kurzem Systemtest) geschafft. Hätte ich die
effizienteste Implementierung gewählt, tüftelte ich noch Tage an der
Separierung von MCU spezifischem Code und Applikation.
Außerdem ist es nett zu sehen, wie eine ganze Library an IC Treibern
automatisch funktioniert, sobald ich für eine neue MCU SPI und I2C
implementiere.
Tom schrieb:> Peter D. schrieb:>> riesen Wust an Code man in C++ für die>> allerkleinsten Aufgaben>> Ein Problem, das so komplex ist, dass abstraktere Programmiertechniken> am Ende einfacher (und weniger fehleranfällig sind) sind, eignet sich> schlecht für ein Mini-Beispiel, das zum Verstehen und im Forum fragen> geeignet ist.>> Die Diskusion läuft hier immer ungefähr so ab:> A: "Ich habe Technik XY entdeckt, mit der man nach 2h Einarbeitung> riesige Probleme flexibel und fehlersicher bearbeiten kann. Hier ein> Miniatur-Beispiel anhand eines LED-Blinkers, wie genau könnte man das am> besten auf das Feature UVW erweitern?">> B: "Du idiot, led blinker sind in retro-C viel einfacher LOL.">> C: "Das ist alles viel zu langsam, in Assembler läuft die Blinkschleife> mit 2 Takten weniger."
Oder so, dass man entweder sein ganzes Projekt postet womit man die
Antwort erntet "Wir entwickeln dir doch nicht dein Projekt"
oder
man kreist sein Problem ein und postet nur den Teil oder etwas
repräsentatives - dann kommt "Ich kann nur aus der Glaskugel den Rest
deines Codes erahnen - poste doch mal alles"
Und dann wird sich gewundert, dass man so passiv-aggresive / arrogante
"Hilfe" nicht haben möchte