Forum: Compiler & IDEs Arduino (Mega): C++ Ereignisbehandlung


von Bert S. (640kb)


Lesenswert?

Hallo Forum,

ich programmiere seit kurzen für den Arduino. Man kommt ja effektiv 
nicht um C++ und eine ordentliche Kapselung herum, wenn man sein 
Programm flexibel halten möchte. Nun stehe ich vor dem Problem, dass ich 
eine Ereignisbehandlung benötige und das gerne so ähnlich wie bei .NET 
haben möchte. D.h. konkret, dass meine Screen Klasse andere Klassen wie 
bspw. meine eigene Button Klasse instanziiert. Und in der Screen Klasse 
möchte ich da mehrere Member Functions als Klick Handler für 
unterschiedliche Button Instanzen haben.

Ich bin noch nicht so der C++ Experte, deswegen hänge ich gerade etwas 
an der Problemaatik. Also Observer Pattern fällt für mich aus, weil ich 
dann ständig alle Instanzen in der Methode checken müsste, um bedingt 
reagieren zu können. std::function gibt es wohl für Arduino nicht.
Function Pointer scheinen so erst mal auch nicht zu funktionieren, wenn 
man Member Functions zuweisen will. Ich habe mal ein Beispiel gesehen, 
wo man eine eigene Event Klasse hatte, die über Templates konkrete T 
Function Pointer entgegen nimmt, aber habs nicht mehr gefunden.

Hat vielleicht jemand eine Idee, wie ich am besten mein Event System 
umsetze?

Danke im Voraus!

Gruß,
640KB

von Pepe T. (pepe_t)


Lesenswert?

Bert S. schrieb:
> Man kommt ja effektiv
> nicht um C++ und eine ordentliche Kapselung herum, wenn man sein
> Programm flexibel halten möchte.

Schwurbel.

von PittyJ (Gast)


Lesenswert?

Arduino ist C++.
Was hindert dich also daran? Lern dann erst einmal die Sprache.

Allerdings ist zu bedenken, dass je nach Architektur wenig Speicher zur 
Verfügung steht. Und dass alles was mit dynamischen Speicher arbeitet, 
irgendwann das System schrottet. (Stichwort Trashing)

Vielleicht solltest du solche Konzepte lieber auf größeren Plattformen 
umsetzen. Aber nicht auch einem AtMega mit 8KByte SRAM.

von Programmierer (Gast)


Lesenswert?

Bert S. schrieb:
> Also Observer Pattern fällt für mich aus, weil ich dann ständig alle
> Instanzen in der Methode checken müsste, um bedingt reagieren zu können.

Du könntest das mit einem User-Data-Argument o.ä. machen. Oder mehrere 
Ereignishandler-Klassen, eine pro Button. Oder eine Konstruktion mit 
CRTP-Observer-Klasse und Mehrfach-Ableitung um die Callback-Funktion 
praktisch mehrfach zu überschreiben. Das kann ich nachher mal ausführen.

Bert S. schrieb:
> Function Pointer scheinen so erst mal auch nicht zu funktionieren,

Doch, allerdings muss es für nicht-statische Funktionen ein 
Member-Function-Pointer sein. Das zu nutzen braucht etwas 
template-Bastelei und type erasure.

Dass jetzt hier sofort die C++-Hasser mit ihren Mistgabeln und Fackeln 
kommen war klar, lass dich davon nicht entmutigen. Das geht alles 
wunderbar, auch auf einem AVR. Nur mit dynamischem Speicher muss man 
aufpassen. Strategy Pattern und Member-Function-Pointer benutze ich 
ständig auf Mikrocontrollern, das ist überhaupt kein Problem.

von Bert S. (640kb)


Lesenswert?

Hallo Programmierer,

danke für die Antwort. Das mit den Templates werde ich mir noch mal 
anschauen. Ich hatte das auch irgendwo schon mal gesehen.

Ja, dynamisch Speicher allokieren im Sinne von zeitlich während der 
Verarbeitung/Loop sollte man vermutlich vermeiden, wegen der 
Fragmentierung. Aber wenn man sozusagen alles notwendige am Anfang 
allokiert und dann alles über ctor injeziert, sollte es ja gehen.

Gruß,
640KB

von Johannes S. (Gast)


Lesenswert?

Bert S. schrieb:
> Ich habe mal ein Beispiel gesehen,
> wo man eine eigene Event Klasse hatte, die über Templates konkrete T
> Function Pointer entgegen nimmt, aber habs nicht mehr gefunden.

im Mbed-os gibt es sowas, ist aber schon recht komplex und das ist nur 
für Cortex-M. Es setzt zwar nicht auf std::function, wird für einen AVR 
aber sicher schnell zu viel und es werden auch andere Klassen der std 
Lib benutzt.
Der Vorteil ist eben das es typsicher ist, im Gegensatz zu generischen 
Funktionszeigern in C.
https://os.mbed.com/docs/mbed-os/v6.15/apis/event-handling-apis.html

von Peter D. (peda)


Lesenswert?

Bert S. schrieb:
> D.h. konkret, dass meine Screen Klasse andere Klassen wie
> bspw. meine eigene Button Klasse instanziiert. Und in der Screen Klasse
> möchte ich da mehrere Member Functions als Klick Handler für
> unterschiedliche Button Instanzen haben.

Statt gleich mit einem riesen Geschwurbel auf die armen Tasten 
einzuprügeln, würde ich schrittweise vorgehen und mich erstmal mit der 
Problematik Entprellen und Flankenerkennung beschäftigen.
Wenn man dann erstmal eine Instanz hat, die fertige Drückereignisse 
liefert, gestalten sich alle nachfolgenden Schritte viel einfacher.

von Johannes S. (Gast)


Lesenswert?

Geschwurbel scheint das neue Lieblingswort hier zu sein.
Wie entprellst du denn Buttons auf einem Screen?

von Peter D. (peda)


Lesenswert?

Johannes S. schrieb:
> Wie entprellst du denn Buttons auf einem Screen?

Arduino verbinde ich gedanklich nicht mit einem Touch-GLCD. Das hätte 
man dazu schreiben müssen.

von Bert S. (640kb)


Lesenswert?

Hallo Peter,

der Code existiert doch schon alles. Ich wollte aber ein paar komplexere 
Sachen darstellen, deshalb habe ich alles refactored und erst mal ein 
gewisses Grundgerüst entwickelt.

Ich habe sowohl eine Button Klasse (für normale Taster) als auch eine 
ScreenButton Klasse. Die ScreenButton Klasse erbt dann von meiner 
VisualTouchElement (macht HitTest) Klasse und die wiederum von der 
VisualElement Klasse (sowas wie ein Canvas).

Konkret habe ich ein 3,5" Touchscreen (480*320) und möchte da ein 
Diorama ansteuern mit ein paar Funktionen. Auf dem Touchscreen mache ich 
ein Startbildschirm mit ein paar Pixel Animationen, Uhr, Wecker, 
Temperatur und Bildschirm Buttons, um bspw. Weckzeit einzustellen oder 
Bitmaps und Sound abzuspielen.

Gruß,
640KB

: Bearbeitet durch User
von Schwurbelmeister (Gast)


Lesenswert?

Bert S. schrieb:
> Auf dem Touchscreen mache ich
> ein Startbildschirm mit ein paar Pixel Animationen, Uhr, Wecker,
> Temperatur und Bildschirm Buttons, um bspw. Weckzeit einzustellen oder
> Bitmaps anzuzeigen.

Ja schon klar, und das alles funktioniert ja leicht auf einem
Arduino Uno, Hardware ist geduldig.

von Johannes S. (Gast)


Lesenswert?

das geht auch gut mit lvgl.io, ist allerdings für AVR zu groß und 
braucht Cortex-M.
Das ist zwar alles in C, lässt sicher gut mit C++ kombinieren weil die 
Basisstrukturen alle einen 'Userdata' Pointer haben. Ein Eventsystem 
gibt es da natürlich auch, eben klassisch mit Callback und fixen 
Argumenten. Funktioniert aber auch alles.

von Programmierer (Gast)


Angehängte Dateien:

Lesenswert?

Peter D. schrieb:
> Wenn man dann erstmal eine Instanz hat, die fertige Drückereignisse
> liefert, gestalten sich alle nachfolgenden Schritte viel einfacher.

Das hat absolut nichts mit der Frage zu tun. Bei einem völlig anderen 
Event, wie z.B. Timer, stellt sich die Frage genau so, aber das hat 
nichts mit Entprellen zu tun. Das sind zwei völlig unterschiedliche 
Baustellen. Hier geht es um Software-Engineering und Patterns. Das ist 
auch kein "Geschwurbel", das ist absoluter Standard wenn es um 
komplexere Software geht. Bert kennt das ja offensichtlich von .net, und 
viele andere Frameworks und Sprachen machen das genau so.

Im Anhang habe ich drei Varianten implementiert.

1. Variante: "FunPointers"

Dies ist die einfachste und klassischste Möglichkeit. Der Button enthält 
ein Array aus gewöhnlichen Funktionspointern und eine Array aus "void* 
user_data"-Pointern pro Callback-Typ, also genau so wie man es in C seit 
Ewigkeiten macht. Dazu gibt es einen kleinen Wrapper "wrapMemFun", 
welchem man einen Member-Funktions-Pointer übergibt und der daraus einen 
normalen Funktions-Pointer mit zusätzlichem user_data-Argument macht. 
Damit kann man dann sehr einfach Member-Funktions-Pointer an die 
Callback-Registrations-Funktionen übergeben:
1
btn0.addClickCallback (wrapMemFun<&Screen::onButton0Click>, this);
Vorteile: Sehr einfach und C-kompatibel

Nachteile: Man kann nicht direkt eine verkettete Liste der Observer 
aufbauen, wenn man die Array-Größe nicht "zufällig" genau richtig 
gewählt hat hat man entweder Speicher verschwendet oder zu wenig 
Speicher, die Fummelei mit userData-Zeigern ist etwas unschön.

2. Variante: "MultiInstanceTag"

Hier wird das klassische Observer-Pattern implementiert. Über den 
"next"-Pointer in der "ButtonObserver"-Klasse wird eine verkettete Liste 
aufgebaut. Die Button-Klasse enthält einen Zeiger auf den ersten 
Observer. Dazu kommt eine "ButtonObserverRelay"-template Klasse, von der 
man dann in der "Screen"-Klasse mehrfach ableiten kann. Die 
"ButtonObserverRelay"-Klasse implementiert die virtuellen Funktionen, 
und leitet diese an die abgeleitete Klasse weiter. Um dort dann zwischen 
den unterschiedlichen Buttons unterscheiden zu können, übergibt man der 
ButtonObserverRelay-Klasse einen "Dummy"-Parameter "Instance", der für 
jeden Button unterschiedlich (aber beliebig) sein muss. Über einen 
zusätzlichen Parameter der Funktionen der Screen-Klasse wird dann 
zwischen den Buttons unterschieden:
1
bool onButtonClick (int x, ButtonObserverRelay<0, Screen>*);
2
bool onButtonClick (int x, ButtonObserverRelay<1, Screen>*);
Vorteile: Beliebig viele Observer ohne dynamische Speicherverwaltung 
oder Speicherverschwendung, relativ einfach, bei vielen Callbacks pro 
Klasse (wie Button) steigt der RAM-Verbrauch nicht an (im Gegensatz zu 
Variante 1)

Nachteile: Funktionsnamen in der Screen-Klasse müssen fix sein (heißen 
bei allen Buttons onButtonClick), daher weniger übersichtlich, 
zusätzlicher Tag-Parameter ist nicht so schön

3. Variante: "MultiInstanceDeclare"

Diese Variante ist der 2. Variante sehr ähnlich und wird nahezu 
identischen Maschinencode produzieren. Das grundlegende Observer-Pattern 
ist identisch implementiert. In der Screen-Klasse werden die gewünschten 
Callbacks über einen Typ deklariert:
1
using DeclareCallbacks = Callbacks<
2
  Callback<Button::OnClick, 0, &Screen::onButton0Click>,
3
  Callback<Button::OnDoubleClick, 0, &Screen::onButton0DoubleClick>,
4
  Callback<Button::OnClick, 1, &Screen::onButton1Click>,
5
  Callback<Button::OnDoubleClick, 1, &Screen::onButton1DoubleClick>
6
>;
Die ButtonObserverRelay-Klasse extrahiert dort heraus die gewünschte 
Callback-Funktion. Die Typen Button::OnClick / Button::OnDoubleClick 
werden nie instanziert, sie dienen nur als Marker zur Unterscheidung der 
Callbacks. Man könnte sie aber auch als Event-Typ einsetzen.
Vorteile: Gleiche wie Variante 2, Namen der Callbacks beliebig wählbar, 
kein Tag-Parameter mehr nötig

Nachteile: Braucht etwas kompliziertere template-Schnipselei in der 
Callbacks-Klasse, die man aber in einer Library verstecken kann

Im Code aller Varianten sieht es so aus, als würden die Callbacks durch 
mehrere Funktionsebenen gehen, was die Performance verringern könnte. 
Der Compiler optimiert das aber weg, insbesondere wenn man LTO nutzt, 
sodass die einzelnen Ebenen ineinander inlined werden. Dadurch ist es 
genau so effizient wie ein "nackter" Funktionszeiger. Tatsächlich sind 
Varianten 2 und 3 je nach Situation auch Speicher-effizienter als die 
klassische C-Version mit Funktionszeigern. Alle drei Varianten brauchen 
keine Standard-Library und sollten daher auch auf dem AVR funktionieren. 
Variante 3 braucht aber C++17. C++11/14 wären auch möglich, wären dann 
aber in der Verwendung etwas lästiger mangels "auto"-NTTP.

von Bert S. (640kb)


Lesenswert?

Schwurbelmeister,

schau dir doch einfach mal ein paar Beispiele an wie bspw. 
MCUFriend_kbv.
Sowas reicht für mir von der Geschwindigkeit erst mal aus.

Eine Bitmap Klasse werde ich auch noch mal selbst neu programmieren. Aus 
dem MCUFriend_kbv Beispiel dauert es ca. 1-2 Sekunden, um ein 480*320 
24Bit Bitmap von SDCard zu zeichnen. Das ist schon ganz OK. Aber man 
kann ja eine Animation machen wie bspw. von innen nach außen zeichen. 
Die Informationen kann man ja aus dem Bitmap Header lesen (Auflösung, 
Offset, Größe).

Also Bitmaps anzeigen und Sound abspielen, habe ich schon prinzipiell 
umgesetzt. Uhr und Temperatur von einem RTC DS3231 habe ich auch schon 
gemacht. Allerdings werde ich die Temperatur über einen eigenen 
Thermistor bestimmen, das ist vom DS3231 unbrauchbar, wegen der Chip 
Temperatur drumherum.

Aber es ging hier ja auch um C++ Ereignisse ;)

Gruß,
640KB

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Bert S. schrieb:
> Eine Bitmap Klasse werde ich auch noch mal selbst neu programmieren. Aus
> dem MCUFriend_kbv Beispiel dauert es ca. 1-2 Sekunden, um ein 480*320
> 24Bit Bitmap von SDCard zu zeichnen.

Es gehört hier eigentlich nicht wirklich hin, aber wenn du den 
"Grafik-Turbo" erleben möchtest, kannst du einen Cortex-M einsetzen der 
ein paralleles Display-Interface (auch RGB-Interface genannt) und eine 
DMA-Einheit besitzt und an ein externes SDRAM angebunden ist. Damit 
kannst du Animationen mit vielen Hz abspielen. Wenn du außerdem ein 
SDIO-Interface (auch SDMMC genannt) für die SD-Karte hast, kannst du 
Daten mit  vielem MByte/Sec einlesen, also praktisch auch Videos 
abspielen.

Boards die so etwas bieten sind z.B. das STM32F746 Discovery oder das 
STM32F429-Discovery. Das ist aber eine ganz andere Hausnummer als der 
Arduino-Mega. Damit macht Grafik/GUI-Programmierung dann richtig Spaß 
- hohe Auflösung und Farbe, komplexe GUIs, Animationen, blitzschnelle 
flüssige Reaktionen, Null Ladezeiten...

von Bert S. (640kb)


Lesenswert?

Programmierer,

besten Dank für die ganzen Hinweise! Ich versuche mich erst mal am 
Arduino Entwickler Board und später mit dem ESP32 (weil ich da einige 
rumliegen habe).
Der ATMega 2560 ist zwar ein 8Biter, aber für mich auch erst mal relativ 
einfach zu verstehen (Assembler will ich auch meine Kenntnisse 
verbessern).

Gruß,
640KB

: Bearbeitet durch User
von PittyJ (Gast)


Lesenswert?

Schwurbelmeister schrieb:
> Bert S. schrieb:
>> Auf dem Touchscreen mache ich
>> ein Startbildschirm mit ein paar Pixel Animationen, Uhr, Wecker,
>> Temperatur und Bildschirm Buttons, um bspw. Weckzeit einzustellen oder
>> Bitmaps anzuzeigen.
>
> Ja schon klar, und das alles funktioniert ja leicht auf einem
> Arduino Uno, Hardware ist geduldig.
>

War auch mein Gedanke dazu. Der TE hat wohl nie die 8Bit vom 1990 Zeit 
erlebt, oder irgendwas mal mit einem Atmel gemacht. Sonst wüßte er die 
Grenzen von 64Kbyte Adressraum.
Wir haben letztens mal ein SPI-Display von einem Arm M7 angesteuert. Und 
selbst da machten die Animationen keinen Spass.

Hallo TE, wenn du es doch auf einem ATMega hinbekommst, dann würde mich 
der Sourcecode interessieren.

von c-hater (Gast)


Lesenswert?

PittyJ schrieb:

> War auch mein Gedanke dazu. Der TE hat wohl nie die 8Bit vom 1990 Zeit
> erlebt, oder irgendwas mal mit einem Atmel gemacht. Sonst wüßte er die
> Grenzen von 64Kbyte Adressraum.

Da kann man einiges drin tun. Man schaue sich doch bloß Spiele oder 
Demos auf den Homecomputern der 8-Bit-Ära an. Und die hatten viel 
weniger Takt und eine deutlich ineffizienteren Befehlssatz als ein AVR8 
(Z80- oder 6502-Derivate mit 1..2MHz).

Nö, warum das heute so "unmöglich" scheint, hat nur einen einzigen 
Grund: hochgeschwurbelte, ineffiziente Hochsprachen. Insbesondere in 
Kombination mit dramatisch unfähigen Programmierern, die zwar 
möglicherweise die Sprache noch einigermaßen beherrschen, aber von der 
Maschine so viel Ahnung haben wie ich vom Plätzchenbacken oder vom 
Häkeln.

> Wir haben letztens mal ein SPI-Display von einem Arm M7 angesteuert. Und
> selbst da machten die Animationen keinen Spass.

Das ist mit an Sicherheit grenzender Wahrscheinlichkeit aber nicht der 
M7 dran Schuld, sondern das über einen "engen" SPI-Pfad angebundene 
Display. Mit einem parallel angesteuerten Display und dem Framebuffer im 
RAM des M7 sieht das schon ganz anders aus...

Das geht dann sicher auch in C oder C++ noch akzeptabel. Aber für die 
kleinen muss man halt das nehmen, was die am besten verstehen: Asm.

von tut nix zur Sache (Gast)


Lesenswert?

Bert S. schrieb:
> Also Observer Pattern fällt für mich aus, weil ich
> dann ständig alle Instanzen in der Methode checken müsste, um bedingt
> reagieren zu können.

Schau dir doch einfach mal MVC Muster an. Lässt sich gut in C++ 
implementieren.


Wenn's zu kompliziert ist, reicht vielleicht auch einfach ein 
Durchreichen eines EventHandlers per Konstruktor.
1
typedef void fnCallback(void);
2
3
class Button
4
{
5
        fnBtnCallback m_onClicked;
6
7
public:
8
        Button( fnBtnCallback obs ) : m_onClicked( obs ) {}
9
        ~Button() {}
10
11
        void handleClick()
12
        {
13
                if( m_onClicked != nullptr )
14
                {
15
                        m_onClicked( this );
16
                }
17
        }
18
};

Memberfunktions-Callbacks gehen auch, musste halt immer gegen das Objekt 
aufrufen, also quasi sowohl den Objektpointer als auch den 
Member-Pointer mitgeben.

Halt so in der Art wie oben schon beschrieben:

typedef void (Button::*fnBtnCallback)( void );

und dann etwa so:

Button btn;
btn->fnBtnCallback();

von Pepe T. (pepe_t)


Lesenswert?

c-hater schrieb:
> hochgeschwurbelte, ineffiziente Hochsprachen

LOL.
Mich stört an C++ die anzahl wrappers um den eigentlichen code. C++ 
macht sachen unübersichtlich und kompliziert.
Es ist mir nicht klar ob diese wrapper auch code erzeugen oder ob die 
wegoptimiert werden.
Die ausrede mit dem "wiederverwendbar" ist wie mit dem "marxismus", 
funktioniert beides nicht :)

von tut nix zur Sache (Gast)


Lesenswert?

Pepe T. schrieb:
> c-hater schrieb:
>> hochgeschwurbelte, ineffiziente Hochsprachen
>
> LOL.
> Mich stört an C++ die anzahl wrappers um den eigentlichen code. C++
> macht sachen unübersichtlich und kompliziert.
> Es ist mir nicht klar ob diese wrapper auch code erzeugen oder ob die
> wegoptimiert werden.

C++ ist da doch harmlos, da kann man immer noch normalen C-Code mit 
schreiben. Guck mal C# an - oder Java, wo jeder int geboxt werden muss 
um in ne Funktion übergeben zu werden. Und "Shy Code" hat man bei C# 
auch nicht gerade erfunden:

objekt1.bla.blub.parameters.foo = objekt2.blub.bli.MeinTollerWert;

> Die ausrede mit dem "wiederverwendbar" ist wie mit dem "marxismus",
> funktioniert beides nicht :)

Wiederverwendbar ist ne Qualität, die man sich richtig erarbeiten muss. 
Viel C/C++-Code leistet in einer Funktion schlicht zu viel auf einmal - 
man braucht nur zu zählen, wieviele Testfälle man für einen Unittest 
schreiben müsste, um jede Datenänderung einer Funktion abzutesten. In 
der Praxis oft schlicht nicht möglich.

von Programmierer (Gast)


Lesenswert?

Könnt ihr euer übliches C++ Haten woanders abladen und hier die 
ernsthaften Entwickler ein produktives Gespräch führen lassen? Danke.

von Pepe T. (pepe_t)


Lesenswert?

Programmierer schrieb:
> ein produktives Gespräch

Der ++ teil von C ist nicht produktiv. SCNR

von PittyJ (Gast)


Lesenswert?

c-hater schrieb:
> PittyJ schrieb:
>
>
>> Wir haben letztens mal ein SPI-Display von einem Arm M7 angesteuert. Und
>> selbst da machten die Animationen keinen Spass.
>
> Das ist mit an Sicherheit grenzender Wahrscheinlichkeit aber nicht der
> M7 dran Schuld, sondern das über einen "engen" SPI-Pfad angebundene
> Display. Mit einem parallel angesteuerten Display und dem Framebuffer im
> RAM des M7 sieht das schon ganz anders aus...
>
Ja, der Flaschenhals war SPI.
Doch der TE will statt eines M7 einen ATMega verwenden. Leistungsmäßig 
noch mal 2 Klassen darunter. Da kann man doch im Ram nicht mal 
ansatzweise einen
Framebuffer bauen.
Dehsalb meine Vermutung, dass der TE nicht genau die Grenzen der 
8-Bitter kennt, und aus einer vollkommen anderen Welt kommt.

von Klaus W. (mfgkw)


Lesenswert?

Ich kann die Intention des TO durchaus verstehen.

Aber erstens geht das alles eher in Richtung Architektur, und dazu ist 
hier zu 98% das falsche Publikum unterwegs.

Und zum anderen ist er dafür tatsächlich auf dem falschen Rechner.
Auf einem AVR kann man sich schon was basteln mit einem Feld, in dem man 
nach Bedarf Funktionszeiger ablegt, über die dann andere Programmteile 
über events informiert können, oder ähnliches.
Aber mit groß OO und Instanziieren würde ich auch eher mit 
Cortex-irgendwas im oberen Bereich anfangen, was Flash und RAM angeht, 
und wo C++ auch schon etwas weiter gediehen ist als bei avr-gcc.

Da kann man dann schöne Dinge treiben.

Die Design patterns der Gang of Four kann man bei AVR nur als 
Lötunterlage benutzen.

: Bearbeitet durch User
von Programmierer (Gast)


Lesenswert?

Klaus W. schrieb:
> Aber mit groß OO und Instanziieren würde ich auch eher mit
> Cortex-irgendwas eher im oberen Bereich anfangen, was Flash und RAM
> angeht

Warum? Ausgerechnet das Arduino-Framework benutzt OOP mit großem Erfolg, 
auch auf AVR. Meine Strategy-Pattern-Beispiele sind sogar RAM-sparender 
als ein Feld aus Funktionszeigern, sofern man mehrere Callbacks im 
Observer hat. Dieses verallgemeinernde "OOP ist ineffizient" ist einfach 
Quatsch. Wenn du das nicht glaubst, schau dir den generierten 
Assemblercode an.

Klaus W. schrieb:
> Die Design patterns der Gang of Four kann man bei AVR nur als
> Lötunterlage benutzen.

Das ist eine haltlose Behauptung. Nicht alle dieser Patterns sind 
sinnvoll für kleine Systeme (z.B. die Factory Patterns), aber andere 
sehr wohl (z.B. State, Strategy, Observer). Wenn die Architektur 
Funktionszeiger unterstützt, funktionieren auch diese Patterns.

von Klaus W. (mfgkw)


Lesenswert?

Klar kann man einiges in ein paar kB reinquetschen.
Aber um eine LED blinken zu lassen, hilft einem die tolle Architektur 
nicht weiter.
Und für ein halbwegs sinnvolles komplexeres Unterfangen wird es schnell 
zu eng für Funktion und schön gleichzeitig.

Wenn man schön als Selbstzweck betrachtet und nicht viel Funktion 
braucht, habe ich nichts dagegen.
Auf Dauer glücklich wird man damit vermutlich nicht.

Aber jeder wie er will...

von Klaus W. (mfgkw)


Lesenswert?

Programmierer schrieb:
> Dieses verallgemeinernde "OOP ist ineffizient" ist einfach
> Quatsch. Wenn du das nicht glaubst, schau dir den generierten
> Assemblercode an.

Das habe ich nie behauptet, im Gegenteil.
Wenn du meine anderen Ergüsse der letzten Jahre hier kennen würdest, 
wüsstest du daß, ich im Gegenteil vieles nützlich finde was z.B. C++ 
mehr bietet als C.
Aber ich weiß eben auch, daß man auf sehr kleinen Systemen wie AVR schon 
genau hinschauen muß, was man anrichtet. Und dann wird ein "ich krieg 
das da alles rein, anstatt einen anderen Rechner zu nehmen" schnell zum 
Selbstzweck.

Nur weil man es vielleicht kann, heißt es nicht daß es auch sinnvoll 
ist.

von Programmierer (Gast)


Lesenswert?

Klaus W. schrieb:
> Aber um eine LED blinken zu lassen, hilft einem die tolle Architektur
> nicht weiter.

Offensichtlich geht es hier nicht um einen LED-Blinker. Und bei einem 
komplexen LED-Blinker, z.B. mit USB- oder Internet-Steuerung und 
riesiger LED-Matrix hilft OOP sicherlich auch.

Klaus W. schrieb:
> Und für ein halbwegs sinnvolles komplexeres Unterfangen wird es schnell
> zu eng für Funktion und schön gleichzeitig.

Das stimmt halt einfach nicht. Je nachdem wie man es macht ist der 
Overhead durch OOP Null oder winzig. Beim Arduino wird selbst das 
Serial.print und seine Varianten über OOP gemacht, und es funktioniert 
super. Bevor man über OOP lästert sollte man sich mal anschauen, was da 
tatsächlich im Hintergrund passiert.

von Klaus W. (mfgkw)


Lesenswert?

Programmierer schrieb:
> bei einem komplexen LED-Blinker

ok, das ist natürlich etwas anderes.

von 640KB (Gast)


Lesenswert?

Hallo Forum,

ich hatte leider keine Zeit mehr bisher mich um das Thema zu kümmern. 
Ich habe erst mal eine einfache simple Lösung implementiert und wollte 
den Code mal schnell sharen:

    class IEventCallback
    {
        public:
            virtual void Execute() = 0;
    };

    template<typename T>
    class EventCallback : public IEventCallback
    {
        public:
            EventCallback(T* instance, void (T::*function)())
                : instance(instance), function(function) {}
            virtual void Execute() override { (instance->*function)(); }
        private:
            void (T::*function)();
            T* instance;
    };


und verwendet wird es so:

EventCallback<MainScreen> *obj = new EventCallback<MainScreen>(this, 
&MainScreen::ButtonClick);
this->button->AddHandler(obj);

void MainScreen::ButtonClick()
{
    Serial.print("button click handler");

    if(this->button->backColor != TFT_BLUE)
        this->button->backColor = TFT_BLUE;
    else if(this->button->backColor != TFT_BLACK)
        this->button->backColor = TFT_BLACK;

    this->button->Invalidate();
};



Der Click Event wird dann im Button ausgelöst, wenn der HitTest (Touch) 
erfolgreich war.

Für Verbesserungsvorschläge und Anmerkungen freue ich mich natürlich 
immer.

Gruß,
640KB

von Bert S. (640kb)


Lesenswert?

^^ war nicht eingeloggt, war mein Beitrag.

von Programmierer (Gast)


Lesenswert?

Das ist jetzt aber eine doppelte Indirektion, also virtuelle Funktion 
Plus Funktionszeiger. Das verschwendet ein paar Takte und Bytes.

von Bert S. (640kb)


Lesenswert?

Ich wusste mir nicht anders zu helfen :-)

Über das Interface mit der virtuellen Funktion möchte ich den 
typisierten Member Function Pointer abstrahieren. Im Button wird dann 
ein Objekt von IEventCallback gespeichert und dann getriggert durch 
Execute().

von Programmierer (Gast)


Lesenswert?

Bert S. schrieb:
> Über das Interface mit der virtuellen Funktion möchte ich den
> typisierten Member Function Pointer abstrahieren.

Du könntest den Member-Funktions-Zeiger als template-Argument übergeben. 
Dann verschwendest du nur noch den Speicher für den instance-Zeiger.

Bert S. schrieb:
> Ich wusste mir nicht anders zu helfen :-)

Es wurden doch genug Lösungen präsentiert...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.