Da ich bei meinen Spielereien mit diversen MCUs (STM32, ESP32) bezüglich der Programmierung immer wieder schnell an meine Grenzen stoße, da ich kaum über C++ Erfahrung verfüge und diese Sprache ganz allgemein nicht so prickelnd finde (um nicht zu sagen, dass ich mittlerweile ein erklärter C++-Hasser bin), bin ich auf der Suche nach einem guten Buch, das sich vor allem C++ widmet, idealerweise auch auf die Einschränkungen in Mikrocontrollerumgebungen eingeht. Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster Linie um C++ - eine Einführung in Mikrocontroller ist nicht so notwendig.
:
Der Autor dieses Buches macht hier gerne dafür Werbung, damit er es nicht immer selber machen muss übernehme ich es diesmal: http://www.ruediger-asche.de/ (obwohl weniger C++) Dann gibt es hier einiges: https://www.grimm-jaud.de/ und zu modernen C++ Features: https://github.com/AnthonyCalandra/modern-cpp-features Zu OO selber finde ich auch das alte Standardwerk von Stroustrup gut, er selber hat auch eine große Linkliste: https://www.stroustrup.com/C++.html Und praktischerweise kann man auch gleich ein C++ orientiertes OS benutzen, namentlich Mbed-OS. Da sind reichlich Tricks und Konzepte drin die man auch erstmal verstehen muss, auf jedenfall hat man eine gute Basis für Komponenten.
C++ ist aber nur sehr eingeschränkt auf µCs zu nutzen. Auf alle Fälle ist es besser als das veralterte C. Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden sind aufgrund mangelnder Resourcen nicht nutzbar. :-<
MaWin schrieb: > Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden > sind aufgrund mangelnder Resourcen nicht nutzbar. :-< Das stimmt so natürlich nicht.
mh schrieb: > Das stimmt so natürlich nicht. Aber sicher doch. Gibts schon was besseres als den 8051? Für einige hier ist die Erde wohl immer noch eine Scheibe.
Johannes S. schrieb: > mh schrieb: >> Das stimmt so natürlich nicht. > > Aber sicher doch. Gibts schon was besseres als den 8051? > Für einige hier ist die Erde wohl immer noch eine Scheibe. Auch auf einen 8051 sollte das gehen, wenn es einen modernen Compiler dafür gibt.
mh schrieb: > Das stimmt so natürlich nicht. An welche STM32 denkst du, bei denen das problemlos geht?
MaWin schrieb: > mh schrieb: >> Das stimmt so natürlich nicht. > > An welche STM32 denkst du, bei denen das problemlos geht? Das problemlos kommt von dir. Probleme gibt es immer. Eben hast du noch behauptet, dass sie nicht nutzbar sind und das ist einfach falsch. MaWin schrieb: > Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden > sind aufgrund mangelnder Resourcen nicht nutzbar. :-<
Aha, geht also nicht, sonst würdest du es ja schreiben. Danke für deine Bestätigung, dass es nicht geht. ;-)
MaWin schrieb: > Die interessanten Dinge wie polymorphe Objekte mit virtuellen Methoden > sind aufgrund mangelnder Resourcen nicht nutzbar. Das stimmt pauschal so nicht! Ich würde es mit C++ auf Mikrocontrollern versuchen. Durch die Kompatibilität mit C kann man ja auch beliebig mischen. Lernt man etwas neues dazu, kann man auch nach und nach von C auf C++ refactorn. Auf einfachen Mikrocontrollern sind Teile der Standardbibliothek nicht nutzbar, weil diese einen Heap oder eine Betriebssystem erfordern. Alles andere ist aber kein Problem. Es könnte sogar sein, dass der Compiler besser optimieren kann oder die Sytax weniger Fehler erlaubt. Und es stehen viele Algorithmen bereits fertig und optimiert zur Verfügung. z.B.
1 | // C
|
2 | void foo(int* data) // Es wird erwatet, dass data 4 Element hat, der Aufrufer sieht das aber nicht. |
3 | {
|
4 | for(int* p = data; p < data + 4; ++p); |
5 | }
|
6 | // C++
|
7 | // Größe wird vom Compiler erzwungen, data kann kein null pointer sein.
|
8 | void foo(std::array<int,4>& data) |
9 | {
|
10 | // Mit range based for ist sofort ersichtlich, dass man über alles iteriert
|
11 | for(int i : data); |
12 | }
|
Und speziell noch zu den virtuellen Methoden. Beispiel ist ein Interface für einen Treiber mit zwei Implementierung, für verschiedene Geräte.
1 | class IDriver |
2 | {
|
3 | public:
|
4 | virtual void start() = 0; |
5 | };
|
6 | |
7 | class Driver_DevA : public IDriver |
8 | {
|
9 | public:
|
10 | void start() override; |
11 | };
|
12 | |
13 | class Driver_DevB : public IDriver |
14 | {
|
15 | public:
|
16 | void start() override; |
17 | };
|
18 | |
19 | void bootSystem(IDriver&); |
20 | |
21 | int main() |
22 | {
|
23 | #ifdef DEV_A
|
24 | // Im Code gibt es eine Sprungtabelle (v_table) für Driver_DevA, egal wie viele Instanzen davon angelegt werden.
|
25 | // Driver_DevA setzt hier den Pointer auf seine Sprungtabelle.
|
26 | Driver_DevA driver; |
27 | #else
|
28 | Driver_DevB driver; |
29 | #endif
|
30 | // Hier greift Polymorphismus, es wird aber weder ein Heap noch RTTI (Run Time Type Infomation) benötigt.
|
31 | bootSystem(driver); |
32 | }
|
In C müsste man dies mit Funktionspointern lösen und kann dabei mehr Fehler machen, weil man es selbst machen muss. Wird start in einer Ableitung nicht implementiert, sagt einem der Compiler dass direkt. Von den Resourcen wäre es das gleiche wie in C per Hand, wenn das Interface mehr als eine Methode hat. Mit neuen C++ Standards ist es auch möglich Funktionen zu schreiben, die garantiert zur Compilezeit ausgewertet werden (constexpr, consteval), ohne dafür den Präprozessor zu misbrauchen. Damit lasse sich z.B. Parameter mit floating point Code berechnen, ohne dass dieser Code später auf dem Controller zur Laufzeit ausgeführt wird.
Beitrag #6684669 wurde von einem Moderator gelöscht.
Beitrag #6684675 wurde von einem Moderator gelöscht.
Beitrag #6684746 wurde von einem Moderator gelöscht.
Beitrag #6684754 wurde von einem Moderator gelöscht.
Hallo Otto, für mein Beispiel brauchst du keinen größeren Controller. Es sollte fast auf das gleiche wie bei C rauskommen, wenn der C Code korrekt war. In C kann man nämlich leicht die Freigabe von Resourcen beim return vergessen, dann ist der C Code natürlich kleiner, aber auch falsch. Ich gebe dir recht, dass C++ komplexer ist, wenn man C gewöhnt ist und sehr hardwarenah denkt. (Bei deinem Kommentar würde ich dich genau hier einordnen.) Deshalb fragt der TO ja auch nach Büchern um das zu lernen oder sein Wissen zu erweitern. Mit C++11 und neueren Standards kann man aussagekräftigen Code schreiben und nicht alles, was in C++ Text ist, produziert auch später Code.
Aber jetzt doch nochmal war hilfreiches für den TO. Ich würde an deiner Stelle erstmal C++ lernen und die Denkweise verinnerlichen. Aus eigener Erfahrung kann das auch etwas dauern. C++ bringt dir erst dann einen Vorteil, wenn du in Objekten denkst und diese nutzt. z.B. * std::array<int, 10> a; std::find(a.begin(), a.end(), 42); statt eine for Schleife, die mit Pointern und Größen manuell arbeitet * Freigabe von Locks am Ende des Funktion, egal wie man aus der Funktion rausspringt. (Scoped Lock) Für Embedded kenne ich keine Bücher, weil ich das nur als Schulung hatte. Bei der Anwendung hängt es von deinem System ab: * OS mit Heap => Es geht wahrscheinlich alles. Interessant ist, wie teuer es dann wirklich ist. * Bare Metal => Die Standardbibliothek ist nur eingeschränkt nutzbar. Sonst kannst du aber alles nutzen, was keinen dynamischen Speicher braucht. Als Resourcen beim Üben kann ich noch folgendes empfehlen: * Compiler Explorer: Hier kann man schnell mal ein C++ Konstrukt eingeben und sich im Assembler anschauen, wie es umgesetzt wird und was eine Optimierung ändert. https://godbolt.org/ * Cpp Core Guidelines: Das sind best Practices für C++ Code. Immer wenn man denkt, dass muss doch besser gehen kann man hier mal reinschauen. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines * Die Referenz, wenn du dir anschauen willst, was die Standardbibliothek bietet. https://en.cppreference.com/w/ Und wenn du deinen Code auch auf einem Desktop bauen kannst, dann ist clang-tidy sehr hilfreich. Das ist eine Codeanalyse, die dir aber in vielen Fällen auch gleich sagt, wie man es besser machen könnte. https://clang.llvm.org/extra/clang-tidy/ Und dann happy coding :-)
Beitrag #6684786 wurde von einem Moderator gelöscht.
Da gab es einen Versuch mit EC++ (Embedded C) : https://de.m.wikipedia.org/wiki/Embedded_C%2B%2B http://www.caravan.net/ec2plus/ Hat sich aber nicht durchgesetzt.
Daniel schrieb im Beitrag #6684786: > C++ und Mikrocontroller. > Warum denn einfach wenns auch kompliziert geht? Wenn dein Hirn zu klein ist um C++ zu verstehen ist das dein Problem, deshalb musst du nicht einen Thread zur Frage nach C++ Literatur voll müllen. Mach einen eigenen Thread auf oder lies erstmal die zigtausend existierenden Beiträge zu diesem Thema. Dieses Unterforum sollte auch für anonyme Störer gesperrt werden, das war bisher ein seriöser Teil des Forums.
Danke, Embedded C++ kannte ich auch noch nicht. Mich wunder aber, dass sie folgender Konstrukte auch weglassen wollten. Namensräume: Das ist ja nur eine Bennenung, die nach der Compilierung sowieso keine Rolle mehr spielt. Vorteilhaft gegenüber den Funktionspräfixen bei C ist, dass man innerhalb des Namensraum (namespace) diese nicht angeben muss. Templates: Es müssen ja nicht gleich die komplizierten sein. Aber wenn ich einen Algorithmus für zwei Datentypen haben möchte, dann habe ich es einmal als Template und erzeuge es dann für beide Typen. Besser als Kopieren.
:
Bearbeitet durch User
C++ Wissen kann die Porgrammierung in C positiv beeinflussen. Z.B. beim Organisieren von Daten durch Kapselung von Daten. https://de.m.wikipedia.org/wiki/Datenkapselung_(Programmierung) Und Erstellung von Zugriffsfunktionen.
:
Bearbeitet durch User
Franz W. schrieb: > Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster > Linie um C++ - eine Einführung in Mikrocontroller ist nicht so > notwendig. Du kannst mal diesen Thread durcharbeiten: Beitrag "Informationen zu C vs C++ / aka Futter für die Diskussion" Da findest Du viele Hinweise.
Kann das Buch "C++ Das umfassende Handbuch" von Torsten T. Will empfehlen. Mit seinem Stil des erklärens kam ich gut zurecht.
Franz W. schrieb: > Da ich bei meinen Spielereien mit diversen MCUs (STM32, ESP32) bezüglich > der Programmierung immer wieder schnell an meine Grenzen stoße, da ich > kaum über C++ Erfahrung verfüge und diese Sprache ganz allgemein nicht > so prickelnd finde (um nicht zu sagen, dass ich mittlerweile ein > erklärter C++-Hasser bin) Nun,ich verstehe dies sehr gut. Insbesondere wenn man vor hat, die Libraries von ST zu verwenden, wird es echt unübersichtlich. Kommt dazu noch sowas wie LWIP, kann man sich von der Übersichtlichkeit verabschieden. Das Ganze ist in C schon eine Herausforderung und wird in C++ durch das ganze Mapping zu einem Kauderwelsch, was man nicht braucht. Ja, man kann alles auch selber schreiben - muss man aber nicht. In C++ gefällt mit das Bilden von Klassen und Methoden, sowie die Möglichkeit der Kapselung von Variablen, sehr gut. Warum C++ auf Microcontrollern so wenig Verwendung findet, ist wohl nur durch den begrenzten Speicherplatz zu erklären. Viele Vorteile der Sprache kosten wohl zuviel Platz.
:
Bearbeitet durch User
Gerhard W. schrieb: > Warum C++ auf Microcontrollern so wenig Verwendung findet, ist wohl nur > durch den begrenzten Speicherplatz zu erklären. Nein, eher durch Vorurteile wie: >Viele Vorteile der > Sprache kosten wohl zuviel Platz.
Johannes S. schrieb: > Gerhard W. schrieb: >> Warum C++ auf Microcontrollern so wenig Verwendung findet, ist wohl nur >> durch den begrenzten Speicherplatz zu erklären. > > Nein, eher durch Vorurteile wie: > >>Viele Vorteile der >> Sprache kosten wohl zuviel Platz. Sehe ich genauso: gerade templates und header-only ermöglichen dem Compiler beste Optimierungsvoraussetzungen, die man eben mit object-libraries nicht hat. Compiletime-Computation spart Laufzeit und/oder externe Tools. Und Meta-Funktionen ermöglichen bestmögliche Expressivität und damit Fehleraufdeckung ebenfalls zur Compilezeit.
Man muss nur etwas länger lernen bis man auf deinem Level ist, dafür haben auch nicht alle die Zeit und Nerven :)
Hallo, laut meiner Erfahrung eignen sich Klassen Templates aber nicht dazu Objekt Instanzen mittels Array anzulegen um damit einfach darüber in einer for Schleife iterieren zu können. Dazu benötigt man wieder 'new' zum initialisieren und für den Zugriff virtuelle Methoden was die Codegröße regelrecht explodieren lässt. Was genau den gegenteiligen Effekt bringt was man vorher mühselig eingespart hat. Deswegen komme ich wieder weg von Klassen Templates. Wenn der Code nicht explodieren soll kann man nur einzelne Instanzen anlegen und muss dann auch alle einzeln behandeln was keinen großen Sinn macht.
:
Bearbeitet durch User
Veit D. schrieb: > laut meiner Erfahrung eignen sich Klassen Templates aber nicht dazu > Objekt Instanzen das ist eine Tautologie: Objekte sind (unbenannte) Instanzen von Typen (Klassen) > mittels Array anzulegen um damit einfach darüber in > einer for Schleife iterieren zu können. Da std::array<> ein homogener Container ist, kannst Du ja nur Objekte eines Typ darin aufnehmen. Veit D. schrieb: > Dazu benötigt man wieder 'new' > zum initialisieren und für den Zugriff virtuelle Methoden was die > Codegröße regelrecht explodieren lässt. Du meinst also hier wohl eher, dass Du ein Array mit Basisklassenzeigern anlegst. Was hat das jetzt mit dem code-bloat durch templates zu tun, wenn die std::array<Base*,N> verwendest? Veit D. schrieb: > Was genau den gegenteiligen > Effekt bringt was man vorher mühselig eingespart hat. Nun ist also Laufzeitpolymorphie doch schlechter in Deinen Augen als statische Polymorphie? Etwas wirr. Veit D. schrieb: > Wenn der Code nicht explodieren soll > kann man nur einzelne Instanzen anlegen und muss dann auch alle einzeln > behandeln was keinen großen Sinn macht. Du meinst statt eines std::array<A,N> dann N-mal eine eigene Variable? Ich glaube, Du sortierst besser erst einmal Deine Gedanken.
Hallo, vielleicht nur unglücklich ausgedrückt. Ich habe zum Bsp. eine Klasse ohne Template. Mit der Klasse kann ich mir ein Array namens object bauen und mittels for darüber iterieren.
1 | class Foo |
2 | {
|
3 | private:
|
4 | const uint8_t member; |
5 | |
6 | public:
|
7 | Foo(byte n) : member {n} |
8 | {}
|
9 | |
10 | uint8_t getData() { return member; } |
11 | };
|
12 | |
13 | Foo object []= |
14 | {
|
15 | {10}, |
16 | {5} |
17 | };
|
18 | |
19 | void setup (void) |
20 | {
|
21 | Serial.begin(250000); |
22 | Serial.println(F("\nµC Reset ### ### ###")); |
23 | |
24 | for (Foo &i : object) { |
25 | Serial.println(i.getData() ); |
26 | }
|
27 | }
|
28 | |
29 | void loop (void) |
30 | { } |
Das geht laut meines Wissens mit einem Klassen Template nicht so leicht. Dieses sieht wie folgt aus. Jedes Objekt muss einzeln angelegt und behandelt werden.
1 | template <const uint8_t n> |
2 | class Foo |
3 | {
|
4 | private:
|
5 | const uint8_t member {n}; |
6 | |
7 | public:
|
8 | Foo() = default; |
9 | |
10 | uint8_t getData() { return member; } |
11 | };
|
12 | |
13 | Foo <10> obj1; |
14 | Foo <5> obj2; |
15 | |
16 | void setup (void) |
17 | {
|
18 | Serial.begin(250000); |
19 | Serial.println(F("\nµC Reset ### ### ###")); |
20 | |
21 | Serial.println(obj1.getData() ); |
22 | Serial.println(obj2.getData() ); |
23 | }
|
24 | |
25 | void loop (void) |
26 | { } |
Um die Klassen Template Objekte als Array anzulegen benötige ich new zum anlegen dieser und eine zusätzliche Schnittstelle die mir durch virtuelle Methoden Zugriff auf die eigentlichen Methoden erlaubt. Genau das lässt den Code explodieren. Oder kennst du eine einfachere Möglichkeit das man diesen Aufwand nicht treiben muss und die Codegröße nicht weiter ansteigt? Wohlgemerkt mit normalen Mitteln was die avr-gcc Toolchain bietet.
Das Klassen Template war noch nicht ganz zu Ende umgeformt. Aber ihr wisst sicherlich auch so was ich meine.
1 | template <const uint8_t n> |
2 | class Foo |
3 | {
|
4 | public:
|
5 | Foo() = default; |
6 | |
7 | uint8_t getData() { return n; } |
8 | };
|
9 | |
10 | Foo <10> obj1; |
11 | Foo <5> obj2; |
12 | |
13 | void setup (void) |
14 | {
|
15 | Serial.begin(250000); |
16 | Serial.println(F("\nµC Reset ### ### ###")); |
17 | |
18 | Serial.println(obj1.getData() ); |
19 | Serial.println(obj2.getData() ); |
20 | }
|
21 | |
22 | void loop (void) |
23 | { } |
Veit D. schrieb: > Um die Klassen Template Objekte als Array anzulegen benötige ich new zum > anlegen dieser und eine zusätzliche Schnittstelle die mir durch > virtuelle Methoden Zugriff auf die eigentlichen Methoden erlaubt. Ja, denn in Deinem Beispiel sind Foo<1> und Foo<2> ja unterschiedliche Template-Instanziierungen und damit unterschiedliche Datentypen. Und in einen homogenen Container kannst Du nur Objekte desselben DT ablegen. (Das const in der Template-Präambel ist btw. überflüssig für NNTP.) Natürlich kannst Du das mit einem Interface für das Template Foo<> umgehen. Also etwa:
1 | struct I{}; |
2 | template<auto N> struct Foo : public I { |
3 | // ...
|
4 | };
|
5 | |
6 | Foo<0> f0{42}; |
7 | Foo<1> f1{43}; |
8 | |
9 | I* a[] {&f0, &f1}; |
Allerdings wäre jetzt die Frage: was modelliert denn Dein template? Eine HW-Geräte-Instanz wie etwa einen Timer? Um heterogene bzw. quasi-heterogene Container zu verwenden, kann man std::variant<> einsetzen (also OHNE den Basistyp I):
1 | std::variant<Foo<0>, Foo<1>> a[] { {42}, {43} }; |
oder
1 | std::variant<Foo<0>*, Foo<1>*> a[] {&f0, &f1}; |
(bzw. statt einem rohen C-Array besser mit std::array<>). Als (echt) heterogener Container dient dann std::tuple<>:
1 | auto a = std::make_tuple(Foo<0>{42}, Foo<1>{42}}; |
Allerdings vermute ich, dass Du ggf. etwas modellieren möchte, was man anders machen sollte. (P.S.: alles obige kann beliebig viele Tippfehler enthalten)
:
Bearbeitet durch User
Hallo, Danke. Das sieht auf den ersten Blick vielversprechend aus. Beim ausprobieren stelle ich aber fest, das struct I keinen Zugriff auf die Member/Methoden von Foo hat. Wie auch, die Vererbung verläuft doch von I nach Foo und nicht umgekehrt.
1 | struct I {}; |
2 | |
3 | template<uint8_t n> |
4 | struct Foo : public I |
5 | {
|
6 | Foo() = default; |
7 | uint8_t getData() { return n; } |
8 | };
|
9 | |
10 | Foo<42> obj0; |
11 | Foo<43> obj1; |
12 | |
13 | I *object[] {&obj0, &obj1}; |
14 | |
15 | void setup (void) |
16 | {
|
17 | Serial.begin(250000); |
18 | Serial.println(F("\nµC Reset ### ### ###")); |
19 | |
20 | Serial.println(obj0.getData() ); |
21 | Serial.println(obj1.getData() ); |
22 | |
23 | for (I &i : object) |
24 | {
|
25 | Serial.println(i.getData() ); |
26 | }
|
27 | }
|
28 | |
29 | void loop (void) |
30 | { } |
Wenn ich das verstanden habe bzw. mein Fehler geklärt ist können wir uns über den Zweck meinen Anliegens unterhalten. Sonst wird es mir zu durcheinander. Eigentlich benötigt man wie du das zeigst new zur Objekt Initialisierung im Array und das struct I beinhaltet die virtuellen Methoden. Ich lande demzufolge immer wieder bei dem Aufbau.
1 | struct Interface |
2 | {
|
3 | virtual uint8_t getData() = 0; |
4 | };
|
5 | |
6 | template<uint8_t n> |
7 | struct Foo : public Interface |
8 | {
|
9 | Foo() = default; |
10 | uint8_t getData() { return n; } |
11 | };
|
12 | |
13 | Interface *foo[2] = |
14 | {
|
15 | new Foo <42>(), |
16 | new Foo <43>() |
17 | };
|
18 | |
19 | void setup (void) |
20 | {
|
21 | Serial.begin(250000); |
22 | Serial.println(F("\nµC Reset ### ### ###")); |
23 | |
24 | for (Interface *i : foo) |
25 | {
|
26 | Serial.println(i->getData() ); |
27 | }
|
28 | |
29 | }
|
30 | |
31 | void loop (void) |
32 | { } |
Veit D. schrieb: > Eigentlich benötigt man wie du das zeigst new zur Objekt Initialisierung > im Array und das struct I beinhaltet die virtuellen Methoden. Ich lande > demzufolge immer wieder bei dem Aufbau. Ich denke in der Realität könnte es auch so aussehen. Dafür wäre dann auch kein Heap notwendig.
1 | // Code der Klassen, wie bei devil-elec
|
2 | struct Interface; |
3 | template<uint8_t n> |
4 | struct Foo : public Interface; |
5 | |
6 | std::array<Interface*, 2> foo; |
7 | |
8 | void setup () |
9 | {
|
10 | Serial.begin(250000); |
11 | Serial.println(F("\nµC Reset ### ### ###")); |
12 | for (Interface *i : foo) |
13 | {
|
14 | Serial.println(i->getData()); |
15 | }
|
16 | }
|
17 | |
18 | int main() |
19 | {
|
20 | // Geräte spezifischer Code
|
21 | Foo<42> interface_A; |
22 | Foo<43> interface_B; |
23 | |
24 | foo[0] = &interface_A; |
25 | foo[1] = &interface_B; |
26 | |
27 | // Allgemeiner Code
|
28 | setup(); |
29 | |
30 | // Man darf main nicht verlassen, sonst wären die Pointer in foo dangling.
|
31 | for(;;); |
32 | |
33 | foo.fill(nullptr); |
34 | }
|
Hallo, Danke, selbst wenn ich das sortiere, ich habe doch gar kein std::array<> zur Verfügung. Ich nutze doch den avr-gcc. Dem fehlt die all umfassende Standard Libray.
Gerald K. schrieb: > C++ Wissen kann die Porgrammierung in C positiv beeinflussen. Z.B. beim > Organisieren von Daten durch Kapselung von Daten. > https://de.m.wikipedia.org/wiki/Datenkapselung_(Programmierung) > > Und Erstellung von Zugriffsfunktionen. Nun ja. Wenn man auf sämtliche Daten einer Klasse über Getter- und Setter-Funktionen zugreifen kann, dann hat man an Datenkapselung nicht wirklich viel gewonnen. ;-) Besser: Objekte tauschen zur Laufzeit Nachrichten miteinander aus. So wie es ursprünglich in Smalltalk gemacht wurde.
Beitrag #6699729 wurde vom Autor gelöscht.
Veit D. schrieb: > Danke, selbst wenn ich das sortiere, ich habe doch gar kein std::array<> > zur Verfügung. Ich nutze doch den avr-gcc. Dem fehlt die all umfassende > Standard Libray. Dann ist es eine gute Übung, sich gerade mal ein std::array<> zu schreiben. Oben stand Arduino, dass muss ja nicht zwingend AVR sein. Aber ich habe es mir schon gedacht, dass Du Arduino/AVR meinst. std::variant<> ist natürlich schon kompilzierter, damit man UB vermeidet. Es schadet aber auch nicht, sich die Implementierung in der stdlib anzuschauen.
:
Bearbeitet durch User
Veit D. schrieb: > Wenn ich das verstanden habe bzw. mein Fehler geklärt ist können wir uns > über den Zweck meinen Anliegens unterhalten. Sonst wird es mir zu > durcheinander. Ich hatte ja geschrieben, dass ich das ohne IDE so herunter getippt habe. Und dabei habe ich es mir einfach gemacht, und Deine Basisklasse/Schnittstelle nicht ausformuliert. Ich dachte, wenigstens das sein klar. Ok, Du hast es dann selbst gewmerkt. Allerdings ist der new-Op wirklich nicht nötig, dass hatte ich Dir oben ja schon gezeigt. Ich bin immer noch der Ansicht, dass Du hier etwas versuchst, was entweder der dyn. oder der statischen Polymorphie entgegen läuft. Eine Mischung von beidem ist meistens nicht gut.
Mark B. schrieb: > Nun ja. Wenn man auf sämtliche Daten einer Klasse über Getter- und > Setter-Funktionen zugreifen kann, dann hat man an Datenkapselung nicht > wirklich viel gewonnen. ;-) Das sollte man ja auch auf keine Falls tun. Auch die Bezeichnung getter-/Setter-Funktionen ist ja m.E. daneben, auch wenn es viel verwendet wird (aber weniger im C++ Umfeld). In C++ ist eine const-Elementfunktion ein Beobachter (getter), und alles was nicht const ist, ist dann ein Mutator (setter). Denn der beobachtbare Zusatnd eines Objektes muss nicht 1:1 aus den Datenelementen des Typs bestehen, sondern kann beliebig daraus berechnet werden.
Hallo, ganz ehrlich, ich komme mir etwas veralbert vor. Weil ohne new geht es nicht. Probiere es bitte aus. Wie es ohne new gehen soll weiß ich nicht und ich kann es auch nicht erkennen. Wenn das alles Quark ist dann zeige mir bitte wie man es anders macht. Im Grunde sollte es im Bsp. auch egal wofür man ein Klassen Template baut. Hier gehts doch nur darum wie man die dann davon instanzierten Objekte mittels for Schleife iteriert, wofür man ein Array benötigt. Ganz am Anfang hatte ich geschrieben das ich einen avr-gcc verwende. Das ist unabhängig ob Arduino oder nicht Arduino. Mit der Arduino IDE bin ich nur schneller am testen. Tief durchatmen ...
Veit D. schrieb: > ganz ehrlich, ich komme mir etwas veralbert vor. Habe ich mich über Dich lustig gemacht?
1 | namespace { |
2 | Foo<42> f0; |
3 | Foo<43> f1; |
4 | }
|
5 | |
6 | Interface* foo[2] = {&f1, &f1}; |
Das hatte ich Dir oben schon geschrieben. Und ändere bitte die Signatur Deines Beobachters ("getters").
Veit D. schrieb: > Hallo, > > ganz ehrlich, ich komme mir etwas veralbert vor. Weil ohne new geht es > nicht. Probiere es bitte aus. Mit welchem konkreten Code und Compiler hast du es denn ausprobiert? Was sagt dir, dass es nicht geht? Wie von wimalopaan und mir gezeigt braucht man dafür keinen Heap, also kein new. Man braucht new nur, wenn man die Objekte dynamisch Objekte anlegen, weil es verschiedene Typen sind.
Hallo, irgendwie hatten wir aneinander vorbei geredet. ;-) So Leute, jetzt habe ich das hinbekommen. Vielen Dank an eure und meine Geduld. :-)
1 | struct Interface |
2 | {
|
3 | virtual uint8_t getData() = 0; |
4 | };
|
5 | |
6 | template<uint8_t n> |
7 | struct Foo : public Interface |
8 | {
|
9 | Foo() = default; |
10 | uint8_t getData() { return n; } |
11 | };
|
12 | |
13 | Foo<42> f0; |
14 | Foo<43> f1; |
15 | |
16 | Interface *foo[2] = {&f0, &f1}; |
17 | |
18 | void setup (void) |
19 | {
|
20 | Serial.begin(250000); |
21 | Serial.println(F("\nµC Reset ### ### ###")); |
22 | |
23 | for (Interface *i : foo) |
24 | {
|
25 | Serial.println(i->getData() ); |
26 | }
|
27 | }
|
28 | |
29 | void loop (void) |
30 | { } |
Das heißt ohne new klappt das aber das Interface mit den virtuellen Methoden benötigt man weiterhin? Richtig? Was ist am Namen getData() verkehrt? Ich hole mir doch Daten mittels einer Methode. Wie benennst du solche Methoden? Sowas nennt man doch Gettermethoden --> get... @ M.K.B. Ich bin von dem unteren Code 13:49 Uhr ausgegangen und habe new weggelassen wollen, was nicht ging. Das ist nun hinfällig. :-) avr-gcc 10.3.0 auf C++20 eingestellt.
Hallo, gleich noch eine Frage. Warum klappt das nicht mit einer Referenz statt Zeiger?
1 | for (Interface &i : foo) |
2 | {
|
3 | Serial.println(i.getData() ); |
4 | }
|
Bei dem range based for muss der Typ links der selbe wie im Container sein oder konvertierbar sein. Interface* kann nicht zu Interface& konvertiert werden. Man könnte Interface*& machen, aber beim Pointer ist das nicht so sinnvoll.
Hallo, verstehe, Danke. Wenn man ohne virtuelle Methoden auskommen möchte, dann benötigt man std::array? Soweit sollte ich dann richtig verstanden haben? An dem Punkt macht es Sinn weiter auszuholen. Wofür die generelle Frage? Meine bisherigen Klassen Templates beziehen sich auf die Hardware eines µC. Aktuell für einen ATmega4809 und AVR128DB48 bezogen. Diese dienen zum konfigurieren, schalten und walten der I/O Pins und zum konfigurieren der Timer TCA und TCB. Bei der Pin Klasse würde ein Array richtig Sinn machen. Beim TCA eher weniger. Beim TCB schon mehr, weil vom TCB Typ mehrere vorhanden sind. Es würde also Sinn machen wenn man über alles bequem iterieren kann. Desweiteren habe ich noch eine Timer Klasse erstellt die als Kurzzeitwecker oder Stoppuhr dienen kann. Die nutzt den millis Zähler von Arduino und ist ansonsten unabhängig. Davon kann man auch mehrere Instanzen gebrauchen. Was wäre die anfangs angesprochene Alternative?
Ich glaube da gibt es noch ein Missverständnis. Die virtuellen Methoden braucht man, damit man über den gemeinsamen Typ des Interfaces verschiedene Ableitungen mit verschiedenen Implementierung ansprechen kann. Damit kann man z.B. eine Methode mit Referenz oder Pointer auf das Interface anlegen, die aber nichts von den möglichen Implementierungen wissen muss.
1 | void setup(Interface& ifc); |
Das Problem mit dem Array und new ist ein anderes. Für den Zugriff auf das Interface reicht ein Pointer/Referenz, um damit arbeiten zu können. Möchte man jedoch die implementierenden Objekte anlegen, dann brauchen diese Speicher, dessen Größe und Initialisierung aber vom konkreten Objekt abhängen. Ein Array kann von seinem Prinzip her aber nur gleiche Typen speichern. Mit new holt man sich den Speicher vom Heap. Das praktische daran ist, dass es reicht wenn man den Pointer auf die Basisklasse behält, um das Objekt später zerstören zu können. Das Array hält dann nur noch die Pointer, die dann wieder vom gleichen Typ sind. Statt Heap könnte man auch noch std::variant für das Array verwenden. Oder man legt sich einen statischen Speicherbereich an, gibt diesen einer std::pmr::monotonic_buffer_resource und legt dort seine Objekte mit std::pmr::polymorphic_allocator an. Das ist aber sehr neu und vermutlich nicht in allen Compilern verfügbar. Wenn es trotzdem jemand ausprobieren will, dann sollten wir dafür aber einen neuen Thread aufmachen.
https://opensource.googleblog.com/2020/03/pigweed-collection-of-embedded-libraries.html ist auch großteils in C++ geschrieben. Virtuelle Funktionen sind natürlich sehr wohl möglich und auch sinnvoll verwendbar. Kommt halt auf die Anwendung drauf an. Die meisten embedded Applikationen sind halt recht statisch und sollten da auch mit einem "statischen" Polymorphismus auskommen, da können templates helfen den Code übersichtlich und sicher zu machen (im Vergleich zu C-Makros). Auch exceptions sind kein Problem und funktionieren ausgezeichnet auf ARM. Man muss halt herumfiddln und newlib-nano mit aktivierten exceptions selber bauen. Insgesamt muss man halt genau wissen welches feature wieviel overhead erzeugt, und man darf sich auch vorm asm listing nicht fürchten.
Veit D. schrieb: > Was ist am Namen getData() verkehrt? Ich hole mir doch Daten mittels > einer Methode. Wie benennst du solche Methoden? Sowas nennt man doch > Gettermethoden --> get... Es geht überhaupt nicht um die Benennung: Hast Du meinen Post weiter oben eigentlich gelesen? Dort stand: Wilhelm M. schrieb: > In C++ ist eine const-Elementfunktion ein Beobachter (getter), und alles > was nicht const ist, ist dann ein Mutator (setter).
Veit D. schrieb: > Was wäre die anfangs angesprochene Alternative? Die Alternative ist parametrische (statische) Polymorphie. Denn die (meisten ;-) HW-Geräte sind nur in endlicher Anzahl vorhanden. Der macht es wenig Sinn, für die 4 TCB der avr0 eine konkrete Klasse zu schreiben. Denn diese kann man beliebig oft instanziieren. Um das zu verhindern, müsste man entweder factorys einsetzen und / oder die Klasse als monostate realisieren. Beides ist eigentlich eine Krücke, weil Fehler erst zur Laufzeit auftauchen, obwohl es ein statisches Problem ist. Die Alternative wäre ein Klassentemplate wie etwa
1 | template<auto N, typename MCU> |
2 | requires(N <4) // c++20 |
3 | struct TCB { |
4 | static_assert(N<4); // pre-c++20 |
5 | // ...
|
6 | };
|
wobei man natürlich den Contraint bzw. die statische Zusicherung besser als Metafunktion über den MCU-Type schreibt, denn nicht jede MCU hat 4 Instanzen den TCB. Also: statt Objekte zu instanziieren (Laufzeit), instanziiert man Templates (Compilezeit). Das ist das grobe Muster, was man beliebig verfeinern kann. Und es wird wohl kaum bis nie vorkommen, dass Du über die Timer iterieren willst. Und wenn doch, dann geht das auch z.B. durch Fold-Expressions ganz einfach und Maschinencode-sparender als zur Laufzeit mit dyn. Polymorphie.
Veit D. schrieb: > Wenn man ohne virtuelle Methoden auskommen möchte, dann benötigt man > std::array? Soweit sollte ich dann richtig verstanden haben? Nein, da besteht kein Zusammenhang. Dein Problem scheint zu sein, dass Du nicht verstanden hast, was dyn. Polymorphie bedeutet. Google mal bzw. schlage eine Buch auf an der Stelle: Liskov'sches Substitutionsprinzip und eben Schnittstellenvererbung sowie Sub-Typ / Super-Typ-Beziehung ("is-a"). Wikipedia geht auch.
Hallo, ich muss das alles erstmal verdauen. Ich danke euch. Mal sehen wie ich damit klar komme. Beim Timer habe ich mit
1 | static_assert(n < 4, "ATmega4809 has only 4 TCB"); |
schon vorgesorgt das man keinen Unsinn macht. :-)
Veit D. schrieb: > Beim Timer habe ich mitstatic_assert(n < 4, "ATmega4809 has only 4 > TCB"); > schon vorgesorgt das man keinen Unsinn macht. :-) Jetzt musst Du allerdings natürlich auch noch unterscheiden, dass die mega0-Serie keinen TCD hat, die avr128db allerdings schon. Wenn Du nun den MCU-Typ als DT modellierst, dann brauchst Du eine Meta-Funktion, um den DT der MCU auf ein Integer abzubilden (type-traits sind eine Form von Meta-Funktionen). Das ist ganz leicht, aber auch ein gutes Beispiel für solche Compilezeit-Berechnungen.
Wenn man Compilerzeitberechnungen macht, die Werte und keine Typen berechnen, dann kann man mit neueren C++ Versionen auch constexpr Funktionen und constexpr if verwenden. Das sieht dann aus wie normaler Code und ist leichter lesbar als Templates.
Man kann auch Typ->Typ, oder Wert->Typ Abbildungen mit constexpr bzw. immediate Funktionen machen, allerdings benötigt der Aufrufer kann immer noch eine extra Typinferenz via decltype, was auch nicht besonders toll aussieht. Ich bevorzuge für die beiden Arten von den vier immer noch traditionelle Meta-Funktionen als template, während ich oft Typ->Wert oder natürlich immer Wert->Wert als reguläre Funktionen schreibe.
Die MCU Abhängigkeit wollte ich einfacher lösen. Jeder Timer hat seine eigene Klasse. TCA, TCB, TCD. Die Klassen werden intern auf die Anzahl der Timer pro Typ je nach MCU begrenzt. Soweit die aktuelle Überlegung dafür. Ganz am Ende gibts MCU spezifisch eine "Hauptklasse" die alle anderen inkludiert was diese MCU an Hardware hat. Wenn ein ATmega4809 keinen TCD hat, dann wird die TCD Klasse einfach nicht inkludiert. Sprich die #include Zeile nicht getippt. Und selbst wenn man das fälschlicherweise machen würde geht das auch nicht, weil die Registeradressen und Bit Definitionen nicht vorhanden wären und mir der Compiler dann sowieso alles um die Ohren haut.
:
Bearbeitet durch User
Veit D. schrieb: > Die Klassen werden intern auf die Anzahl der Timer pro Typ je nach MCU > begrenzt. Ja, genau das meine ich ja (s.o.). Dazu kannst Du dann die Anzahl aus dem MCU-Typ berechnen, bei anderer interner Peripherie ist das ja genauso. Und das Nichtvorhandensein entspricht dann Anzahl 0.
Franz W. schrieb: > Hat jemand von euch hier einen Tipp ? Mir geht es wie gesagt in erster > Linie um C++ - eine Einführung in Mikrocontroller ist nicht so > notwendig. Weiß nicht, ob's noch notwendig ist, aber als gute Bücher zum Thema habe ich die folgenden selber gekauft und kann die bedenkenlos empfehlen: - Stephan Roth: Clean C++ - Bruce Eckel: Thinking in C++ - Andrei Alexandrescu: Modern C++ Gruß, db8fs
So, da hab ich ja was losgetreten... Danke für die Literaturtipps - ich hab mir folgende Bücher gekauft und wollte meinen Eindruck schildern: Programming Principles and Practice Using C++ (Stroustroup) - verständlich geschrieben - sehr umfangreich - hat sogar ein eigenes Kapitel, das sich mit den Einschränkungen der Embedded-Programmierung beschäftigt (Memory-Fragmentation etc.) - Standardwerk, daher sind die Beispiele vielleicht etwas gekünstelt bzw. von meinem Bedarf entfernt C++ Primer (Lippmann, Lajoje, Moo) - noch verständlicher geschrieben - wirkt auf mich etwas kompakter - wenn ich was suche, schaue ich zuerst in dieses Buch - wirkt auf mich etwas praxisnäher als der Stroustroup (wobei der für ein Standardwerk auch recht zugänglich ist) Den Rest hole ich mir über diverse youtube-Videos. Von C++ verwende ich nur einen kleinen Teil, aber der ist mir den Wechsel von C allemal wert: * weit besseres Typsystem mit strikteren Compiler-Regeln * daher mehr Fehler zur Compile-Zeit erkennbar * Namespaces * Klassen (ohne komplizierte Vererbungskonstrukte) * die etwas modernere Syntax C++ habe ich zuletzt irgendwann in den 90er-Jahren programmiert, konnte mich aber nie wirklich damit anfreunden. Allerdings muss ich zugeben, dass es hier in den letzten 30 Jahren eine offensichtliche Weiterentwicklung gegeben hat und das heutige C++ (11/14) schon um einiges moderner ist als damals. Nichts desto trotz ist es leider recht mühsam, auf dynamische Speicherreservierung zu verzichten und so gleich mal die std-lib auszuschließen. Jede String-Manipulation mit char* fühlt sich an wie Long Covid, aber das liegt nicht an der Sprache, sondern an der Zielumgebung.
Hallo, ich habe nochmal klassische Lib mit .h/.cpp mit der Klassen Template Variante verglichen. Irgendwie stört mich das mit jeder virtuellen Methode, die nur zur Verfügung steht aber nicht genutzt werden muss, der Flashverbrauch immer weiter ansteigt. Man hat zwar vom Flash eigentlich immer genügend nur wenn das mehr Libs werden wird das unschön. Jetzt bin ich erstmal bei der klassischen .h/.cpp Variante hängen geblieben. Hierbei stellt sich mir die Frage warum beim Pin Toggle ein Pegelwechsel 2µs dauert. Alle verwendeten Variablenwerte sind const bzw. wo immer möglich constexpr. Aus meiner Sicht ist alles zur Compiletime bekannt. Aus einer Port Basisadresse wird mittels Offset das eigentliche Register ermittelt und dann mit einer Bitmaske geändert. Mehr ist das nicht. Das Einzigste was ich nicht constexpr machen kann ist die letztlich verwendete Methode toggle(). Warum weiß ich nicht. Kann es sein das das mit getrennten .h/.cpp einfach nicht geht? Beim Versuch bekomme ich immer "error: inline function 'constexpr void OutputP::toggle() const' used but never defined [-Werror]". Lasse const weg Lasse ich const weg bekomme ich: "error: inline function 'constexpr void OutputP::toggle()' used but never defined [-Werror]".
1 | // .h
|
2 | constexpr void toggle() const; |
3 | |
4 | //.cpp
|
5 | constexpr void OutputP::toggle() const { *regVPORTin(pin) = getMask(pin); } |
Gibt es eine Möglichkeit das zu beschleunigen bzw. zur Compiletime erledigen/auflösen zu lassen? So sieht das derzeit aus ... Registersatz
1 | #pragma once
|
2 | |
3 | namespace Pins |
4 | {
|
5 | namespace Addr |
6 | {
|
7 | // Arduino Nano Every
|
8 | struct Offset |
9 | {
|
10 | // VPORTs
|
11 | const uint8_t vDir = 0x00; |
12 | const uint8_t vOut = 0x01; |
13 | const uint8_t vIn = 0x02; |
14 | const uint8_t vFlag = 0x03; |
15 | // PORTs
|
16 | const uint8_t dir = 0x00; |
17 | const uint8_t dirSet = 0x01; |
18 | const uint8_t dirClear = 0x02; |
19 | const uint8_t dirToggle = 0x03; |
20 | const uint8_t out = 0x04; |
21 | const uint8_t outSet = 0x05; |
22 | const uint8_t outClear = 0x06; |
23 | const uint8_t outToggle = 0x07; |
24 | const uint8_t in = 0x08; |
25 | const uint8_t flag = 0x09; // Interrupt Flag |
26 | const uint8_t portCtrl = 0x0A; // Slewrate Bit |
27 | };
|
28 | constexpr Offset addrOffset; |
29 | |
30 | struct Address // base addresses of registers |
31 | {
|
32 | const uint16_t vport; |
33 | const uint16_t port; |
34 | const uint8_t pinCtrl; |
35 | const uint8_t mask; |
36 | };
|
37 | |
38 | // The index is always the Arduino pin number.
|
39 | constexpr Address baseAddr[] |
40 | {
|
41 | // | VPORT | PORT | PINn | BIT |
|
42 | // | Base | Base | CTRL | MASK | // BIT | PIN
|
43 | {0x0008, 0x0440, 0x15, 0x20}, // 5 | 0 |
44 | {0x0008, 0x0440, 0x14, 0x10}, // 4 | 1 |
45 | {0x0000, 0x0400, 0x10, 0x01}, // 0 | 2 |
46 | {0x0014, 0x04A0, 0x15, 0x20}, // 5 | 3 |
47 | {0x0008, 0x0440, 0x16, 0x40}, // 6 | 4 |
48 | {0x0004, 0x0420, 0x12, 0x04}, // 2 | 5 |
49 | {0x0014, 0x04A0, 0x14, 0x10}, // 4 | 6 |
50 | {0x0000, 0x0400, 0x11, 0x02}, // 1 | 7 |
51 | {0x0010, 0x0480, 0x13, 0x08}, // 3 | 8 |
52 | {0x0004, 0x0420, 0x10, 0x01}, // 0 | 9 |
53 | {0x0004, 0x0420, 0x11, 0x02}, // 1 | 10 |
54 | {0x0010, 0x0480, 0x10, 0x01}, // 0 | 11 |
55 | {0x0010, 0x0480, 0x11, 0x02}, // 1 | 12 |
56 | {0x0010, 0x0480, 0x12, 0x04}, // 2 | 13 |
57 | {0x000C, 0x0460, 0x13, 0x08}, // 3 | 14 |
58 | {0x000C, 0x0460, 0x12, 0x04}, // 2 | 15 |
59 | {0x000C, 0x0460, 0x11, 0x02}, // 1 | 16 |
60 | {0x000C, 0x0460, 0x10, 0x01}, // 0 | 17 |
61 | {0x0014, 0x04A0, 0x12, 0x04}, // 2 | 18 |
62 | {0x0014, 0x04A0, 0x13, 0x08}, // 3 | 19 |
63 | {0x000C, 0x0460, 0x14, 0x10}, // 4 | 20 |
64 | {0x000C, 0x0460, 0x15, 0x20}, // 5 | 21 |
65 | };
|
66 | }
|
67 | }
|
.h
1 | #pragma once
|
2 | |
3 | #include <Arduino.h> |
4 | |
5 | class OutputP |
6 | {
|
7 | private:
|
8 | const uint8_t pin; |
9 | |
10 | public:
|
11 | OutputP (uint8_t p); |
12 | |
13 | void init(); |
14 | void toggle() const; |
15 | };
|
.cpp
1 | #include <NanoEveryPinNeu.h> |
2 | |
3 | #if defined(ARDUINO_AVR_NANO_EVERY) && defined(__AVR_ATmega4809__)
|
4 | #include "include/NanoEveryRegister.h" |
5 | #else
|
6 | #error "This library only tested with Arduino Nano Every Board (ATmega4809)."
|
7 | #endif
|
8 | |
9 | using Register8 = volatile uint8_t*; |
10 | |
11 | // ------------------ VPORTs -------------------- //
|
12 | // Direction
|
13 | constexpr Register8 regVPORTdir(const uint8_t pin) { using namespace Pins::Addr; return (Register8) (baseAddr[pin].vport + addrOffset.vDir); } |
14 | |
15 | // Input Level Status
|
16 | constexpr Register8 regVPORTin(const uint8_t pin) { using namespace Pins::Addr; return (Register8) (baseAddr[pin].vport + addrOffset.vIn); } |
17 | |
18 | // Pin Bitmaske
|
19 | constexpr uint8_t getMask(const uint8_t pin) { using namespace Pins::Addr; return (baseAddr[pin].mask); } |
20 | |
21 | OutputP::OutputP(uint8_t p) : pin {p} |
22 | {}
|
23 | |
24 | // Outputs
|
25 | void OutputP::init() { *regVPORTdir(pin) = *regVPORTdir(pin) | getMask(pin); } |
26 | |
27 | void OutputP::toggle() const { *regVPORTin(pin) = getMask(pin); } |
Veit D. schrieb: > Irgendwie stört mich das mit jeder virtuellen > Methode, die nur zur Verfügung steht aber nicht genutzt werden muss, der > Flashverbrauch immer weiter ansteigt. Das Thema zieht sich ja nun schon durch deine ganzen Beträge dieses Subthemas hier, aber nach wie vor ist völlig unklar, was virtuelle Funktionen mit Templates zu tun haben sollen. Und warum man virtuelle Funktionen zur Verfügung stellen sollen müsste, die gar nicht genutzt werden, musst du auch mal erklären. Abgesehen davon, daß der Compiler die bei der Optimierung einfach rauswirft. Das einzige, was bei virtuellen Funktionen in Zusammenhang mit Arduino-AVR wirklich stört, ist die unschöne Platzierung der vtables im SRAm durch den avr-gcc. Irgend etwas wirfst du da noch ganz gehörig durcheinander. Veit D. schrieb: > Alle verwendeten Variablenwerte sind const bzw. > wo immer möglich constexpr. Aus meiner Sicht ist alles zur Compiletime > bekannt. Dann wird der Compiler das auch zur Compilezeit auswerten, und das auch ohne constexpr. Allerdings nur, wenn er das auch kann, d.h. er kann alle const(expr)-Deklarationen sehen. Bei solchen Fingerübungen muß man halt immer den generierten Asemblercode anschauen. Oliver
:
Bearbeitet durch User
Oliver S. schrieb: > Das einzige, was bei virtuellen Funktionen in Zusammenhang mit > Arduino-AVR wirklich stört, ist die unschöne Platzierung der vtables > im SRAm durch den avr-gcc. Naja, jede virtuelle Funktion kostet damit 2 Byte im SRAM. Lägen die Vtables im Flash, bräuchte jeder virtuelle Funktionsrufruf ein paar Taktzyklen mehr, was auch nicht perfekt ist. Wenn man virtuelle Funktionen irgendwo wirklich sinnvoll einsetzen kann, ist IMHO beides ganz gut verschmerzbar.
Hallo, virtuelle Methoden werden nicht rausgewurfen. Auch dafür habe ich ein Assembler Listing gemacht. Ich erkläre es nochmal. Wenn ich mit mehreren erstellten Objekten eines Klassentemplates das selbe machen möchte, wäre es ja blöd immer alle einzeln zu behandeln was im Bandwurm endet. Wofür wurden Arrays und for Schleifen erfunden? Genau das geht nun einmal nicht mit Klassen Templates. Darum braucht es ein zusätzliches Interface mit virtuellen Methoden. Ich habe das nochmal am Beispiel kompilieren lassen. Ein Objekt eines Klassen Templates erstellt. Ohne virtuelle Methoden, die sind in der Lib noch auskommentiert. Sprich einen Output Pin initialisiert, 1x init und 1x toggle aufgerufen. Macht 806 Byte Flash und 22 Byte RAM. Soweit alles okay. Danach init und toggle als virtuelle Methode zur Verfügung gestellt. Im Code noch nicht genutzt. Macht 880 Byte Flash und 24 Byte RAM. Danach alle 11 Methoden virtuell zur Verfügung gestellt. Im Code noch nicht genutzt. Macht 1006 Byte Flash und 24 Byte RAM. Nehme ich so einen zweiten Pin dazu macht das 1172 Byte Flash und 26 Byte RAM. Werfe ich die beiden Pin Objekte in ein Array und nutze damit die virtuellen Methoden jeweils 1x pro Objekt macht das 1250 Byte Flash und 30 Byte RAM. Schaut man sich das Assembler Listung an sieht man ab Zeile 114 alle virtuellen Methoden.
1 | #include <NanoEveryPin.h> |
2 | |
3 | OutputPin <12> led12; |
4 | OutputPin <13> led13; |
5 | |
6 | Interface *outpin[2] = {&led12, &led13}; |
7 | |
8 | void setup(void) |
9 | {
|
10 | for (Interface *i : outpin) |
11 | {
|
12 | i->init(); |
13 | }
|
14 | |
15 | }
|
16 | |
17 | void loop(void) |
18 | {
|
19 | for (Interface *i : outpin) |
20 | {
|
21 | i->toggle(); // 1,25µs |
22 | }
|
23 | }
|
Mache ich das wie anfangs mit 2 Objekten ohne virtuelle Methoden und damit ohne Array macht das 812 Byte Flash und 22 Byte RAM. Jetzt kann sich jeder ausrechnen wohin das explodiert mit den virtuellen Methoden. Das new weglassen macht keinen Unterschied. Etwas langsamer taktet ein Pin mittels virtuellen Methoden auch. 1,25µs für einen Pegelwechseln. Ohne sind 125ns drin. Wenn mir das Oliver ohne virtuelle Methoden zeigen könnte wäre ich sehr dankbar. ;-) Meine Frage lautet bekommt man die klasssische Varianten .h/.cpp noch weiter optimiert? Sodass zur Compilezeit noch mehr aufgelöst werden kann? Wie das alles ausschaut habe ich gezeigt. Es steht im Grunde alles vorher fest. Nur die Methode toggle lässt sich nicht inline und auch nicht constexpr machen. Wobei er es auch selbst optimieren könnte aber nicht macht. Ansonsten müßte ich mich entscheiden ob ich Schnelligkeit hätte oder einen Code Bandwurm. Jedesmal die Lib zu ändern oder unterschiedliche Libs anzulegen macht ja auch keinen Sinn. Man wäre nur am ändern und bekommt keine Ruhe rein. Deswegen der Aufwand und meine Fragen.
(wieder gelöscht...) Um mehr sagen zu können, müsste man den aktuellen Stand sehen. Am besten mit den bisherigen Tipps eingepflegt, wie z.B. getter-Methoden als const.
:
Bearbeitet durch User
Veit D. schrieb: > Wofür wurden Arrays und for Schleifen erfunden? Genau > das geht nun einmal nicht mit Klassen Templates. Natürlich geht das. Das hatte ich schon zigmal gesagt und Dir auch Beispiel-Code dazu gegeben. Du brauchst kein Laufzeit-Interface. Irgendwie habe ich das Gefühl, dass Du das entweder gar nicht liest, nicht durchdenkst oder nicht willst. Du kannst sowohl eine Laufzeit-Iteration formulieren (die der Compiler bis zu einer begrenzten Array-Größe auch ausrollt) oder auch eine Compile-Zeit-Iteration benutzen.
Wilhelm M. schrieb: > Du kannst sowohl eine Laufzeit-Iteration formulieren (die der Compiler > bis zu einer begrenzten Array-Größe auch ausrollt) Allerdings ist der Aufwand dafür ohne verfügbare C++-Standardlib, d.h. ohne tuple, variant, array, etc., doch schon etwas größer. Aber die eigentliche Frage ist doch: was ist das Ziel der ganzen Übung? Oliver
Veit D. schrieb: > Nur die Methode toggle lässt sich nicht inline und auch > nicht constexpr machen. Wobei er es auch selbst optimieren könnte aber > nicht macht. Als Denkübung könntest du ja einmal beschreiben, was genau ein zur Compilezeit per constexpr auswertbares toggle eigentlich tun soll. Das hilft dir vielleicht, die Abgrenzungen der zur Compilezeit bekannten, statischen constexpr-Template-Typenwelt von der dynamischen Laufzeitwelt besser zu erkennen. Oliver
:
Bearbeitet durch User
Das Buch ist nicht wirklich für einen absoluten C++-Anfänger geeignet, ich fand es aber interessant: Real-Time C++ Efficient Object-Oriented and Template Microcontroller Programming https://www.springer.com/de/book/9783662567173
Auch das Buch (in einer vorigen Auflage) steht schon in der Liste in diesem Beitrag drin (hatte ich hier sehr weit oben schon erwähnt, wird natürlich jetzt nicht mehr gelesen): Beitrag "Informationen zu C vs C++ / aka Futter für die Diskussion" Und noch viel mehr.
Christian T. schrieb: > Das Buch ist nicht wirklich für einen absoluten C++-Anfänger geeignet, > ich fand es aber interessant: > > Real-Time C++ > > Efficient Object-Oriented and Template Microcontroller Programming > > https://www.springer.com/de/book/9783662567173 Das Buch selbst kenne ich nicht, die Vorschau sieht aber ganz gut aus. Wichtig ist ja nicht, ob C oder C++, sondern was die einzelnen Konstrukte kosten (Laufzeit, Speicher, Entwicklungszeit). Damit kann man dann auch fundiert entscheiden, ob man es verwenden will/kann.
Wilhelm M. schrieb: > Auch das Buch (in einer vorigen Auflage) steht schon in der Liste in > diesem Beitrag drin (hatte ich hier sehr weit oben schon erwähnt, wird > natürlich jetzt nicht mehr gelesen): Ich denke um solche Duplikate zu vermeiden ist es sinnvoll wenigstens den Titel des Buches zu erwähnen, danach hatte ich hier gesucht, aber nicht jeden Link in jedem Beitrag geclickt.
Beitrag #6713798 wurde von einem Moderator gelöscht.
Veit D. schrieb: > ich habe nochmal klassische Lib mit .h/.cpp mit der Klassen Template > Variante verglichen. und wie war jetzt die Buchempfehlung?
Hallo, @ Wilhelm: Das ist ganz schön starker Tobak von dir. Ohne Interface und ohne C++ Standardlib ist das möglich? M.K.B. hatte ich auch so verstanden. Ohne C++ Standardlib benötigt man virtuelle Methoden. @ Klaus: Wenn du die Lib mit Klassen Template meinst kann ich heute Abend bereitstellen. Das Ziel ist ich möchte mir eine Lib schreiben um Pins zu verwalten. Eingänge lesen, Ausgänge schalten. Das alles möglichst schlank und schnell. Und es muss für eine Array Objektverwaltung nutzbar sein. Bis jetzt kann ich entweder das Eine oder das Andere machen. Beides zusammen funktioniert wie oben beschrieben. Mit Array und mittels for Schleifen Iteration kann man dann zum Bsp. Eingänge mit Ausgängen verknüpfen zum schalten und walten. > Als Denkübung könntest du ja einmal beschreiben, was genau ein zur > Compilezeit per constexpr auswertbares toggle eigentlich tun soll. Das > hilft dir vielleicht, die Abgrenzungen der zur Compilezeit bekannten, > statischen constexpr-Template-Typenwelt von der dynamischen > Laufzeitwelt besser zu erkennen. Genau darum geht es. Ich kann nicht erkennen warum das der Compiler ohne Klassen Template nicht zur Compilezeit auswerten kann. In beiden Fällen müßte er erkennen das er nur eine Bitmaske in VPORTIN schreiben muss und das an Ort und Stelle einfügen soll. Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist die toggle Methode inline oder constexpr zu machen. Wenn die Compilezeitauswertung ja/nein genau der Unterschied zwischen Klassen Templates und non Templates ist, dann könnt ihr das ruhig sagen. Weil genau das steht in keinem Buch und ist keinem Bsp. der Welt erkennbar. Ich sehe nur das alle Bsp. Header only sind. Aber nirgends steht das es nur Header only überhaupt möglich ist. @ Johannes S: Ich habe schon mitbekommen das es komplett offtopic gewurden ist. Aus meinem Kommentar wurde eine Unterhaltung. Nur wenn ich jetzt einen Thread eröffne fange ich bei Null an. Also was soll ich machen?
Veit D. schrieb: > @ Klaus: > Wenn du die Lib mit Klassen Template meinst kann ich heute Abend > bereitstellen. gerne, aber doch bitte nicht in diesem Thread. Was hat das bitte mit einer Buchempfehlung zu tun?
Veit D. schrieb: > Das ist ganz schön starker Tobak von dir. Ohne Interface und ohne C++ > Standardlib ist das möglich? Ja. Auch das habe ich Dir schon gezeigt weiter oben. Ich habe Dir auch gesagt, dass selbst wenn Dur keine stdlib hast, Dir ein tuple oder variant selbser schreiben kannst. Und wenn das auch nichts für Dich ist, dann google einfach mal nach "fromscratch c++". Dann hast Du alles was Du brauchst mundgerecht. Veit D. schrieb: > Und es muss für eine Array Objektverwaltung nutzbar sein. Auch da hatte nicht nur ich sondern auch einige andere auch nach gefragt, warum Du das möchtest. Die Frage hast Du nicht beantwortet. Auch hatte ich Dir erklärt, warum es (meistens) nicht notwendig ist, HW-Ressourcen mit Instanzen von Klassen zu modellieren, sondern statische Template-Instanzen zu verwenden. Anscheinend ist das an Dir vorbei gegangen. Veit D. schrieb: > Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist > die toggle Methode inline oder constexpr zu machen. Natürlich kann sie inline sein. Funktionstemplates sind es automatisch. constepr nicht, weil darin ein reinterpret_cast verborgen sein wird. Lies Dir mal durch, was constexpr / consteval und constinit bedeutet. Veit D. schrieb: > Wenn die Compilezeitauswertung ja/nein genau der Unterschied zwischen > Klassen Templates und non Templates ist, dann könnt ihr das ruhig sagen. Was soll das bedeuten? Du drückst Dich sehr unspezifisch aus. Veit D. schrieb: > Weil genau das steht in keinem Buch und ist keinem Bsp. der Welt > erkennbar. Ich sehe nur das alle Bsp. Header only sind. Aber nirgends > steht das es nur Header only überhaupt möglich ist. Hast Du schon mal darüber nachgedacht, warum templates in header üblicherweise stehen? Hast Du schonmal irgendein Buch darüber gelesen oder versucht zu verstehen.
Veit D. schrieb: > Mir hat noch niemand die Frage beantwortet ob es überhaupt möglich ist > die toggle Methode inline oder constexpr zu machen. Inline ist gar kein Problem. Bezgl. constexpr hast meine Frage überhaupt nicht verstanden. Ich stelle die mal anders: Erkläre mal den Sinn einer constexpr Funktion vom Typ void. Oder halt, was ein constexpr Pin-toggle sein soll. Veit D. schrieb: > Ich kann nicht erkennen warum das der Compiler ohne > Klassen Template nicht zur Compilezeit auswerten kann. Der Compiler kann und wird alles zur Compilezeit auswerten, was er zur Compilezeit auswerten kann, egal, ob da nun Template oder constexpr dransteht oder auch nicht. Oliver
:
Bearbeitet durch User
Veit D. schrieb: > M.K.B. hatte ich auch so verstanden. Ohne C++ Standardlib benötigt man > virtuelle Methoden. Das sehe ich nicht so. Du kannst alle C++ Features nutzen. Evtl. fallen new/delete weg, weil du auf dem Target keinen Heap hast. Die Standardbibliothek bietet für viele Fälle schon fertig Algorithmen oder Typen. Wenn diese aber nicht verfügbar sind oder die Implementierung zu teuer, muss man diese aber nicht nutzen. Im Prinzip kann man alles aus der Bibliothek auch selbst nachbauen.
Fortsetzung hier: Beitrag "Wie Klassen-Template Objekte in einem Array nutzen (non Template, constexpr, inline)"
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.