Forum: Mikrocontroller und Digitale Elektronik STM32 / C++: Initialisierung von Memberobjekten einer Klasse


von Karsten D. (kenny7537)


Lesenswert?

Guten Abend,

ich schlage mich gerade mit der C++ Programmierung auf einem STM32F446 
herum, das Problem ist aber insgesamt recht allgemein.

Nämlich habe ich eine Klasse, in der Objekte einer anderen Klasse 
enthalten sind. Um deren Initialisierung geht es mir.

Beispielsweise mein Uart:
1
Uart.h
2
class Uart
3
{
4
  Uart(instanz);
5
6
  private:
7
    OutputPin TxPin;  //Memberobjekte
8
    InputPin RxPin;
9
}

InputPin und OutputPin sind Klassen, die ich erstellt habe, deren 
Konstruktor konfiguriert unter anderem den Pin, und bekommt Pin und Port 
übergeben.
Je nach dem, welche Instanz ich dem Uart-Konstruktor übergebe, soll der 
entsprechende Pin konfiguriert werden.
1
Uart.cpp
2
Uart::Uart(instanz)
3
{
4
  switch(instanz)
5
  {
6
    case UART1:
7
      TxPin(portA, Pin3);  //Konstruktoraufruf
8
      RxPin(portA, Pin5);
9
      break;
10
    
11
    //usw
12
  }
13
}

Leider geht das nicht. Es funktioniert nur:
1
Uart::Uart(instanz) : TxPin(portA, Pin3), RxPin(portA, Pin5)
2
{
3
  //usw...
4
}

Hier muss ich mich aber zuvor entscheiden, wie die Pins konfiguriert 
werden sollen, was ich ja aus offensichtlichen Gründen nicht kann.

Was funktioniert, ist die Definition der beiden Pins als Referenz im 
Header:
1
    OutputPin *TxPin;
2
    InputPin *RxPin;

Danach kann ich dann die Pins mittels "new" zur gewünschten Zeit 
erstellen:
1
Uart.cpp
2
Uart::Uart(instanz)
3
{
4
  switch(instanz)
5
  {
6
    case UART1:
7
      TxPin = new OutputPin(portA, Pin3);
8
      RxPin = new InputPin(portA, Pin5);
9
      break;
10
    
11
    //usw
12
  }
13
}

Soweit so gut, was habe ich mich gefreut!
Dummerweise bin ich dann bei der Lösung eines anderen Problems über 
dieses Thema gestolpert:
Beitrag "STM32 C++ Memberfunktion in Interrupt"
Wo sich ein forumskollege vehement gegen new ausspricht...

Daher, ganz lange Rede aber kurze Frage:
Wie löse ich mein Problem denn ohne new?

Viele Grüße
Karsten

von Richard (Gast)


Lesenswert?

Karsten D. schrieb:
> Guten Abend,
>
> ich schlage mich gerade mit der C++ Programmierung auf einem STM32F446
> herum
> Wie löse ich mein Problem denn ohne new?

In Assembler. Denn es geht auch ganz ohne die künstlichen Probleme einer 
komplexen Sprach-Syntax.

von nfet (Gast)


Lesenswert?

Was gegen new spricht: es ist leicht falsch zu verwenden. Falsch soll 
hier heißen, dass es immer wieder aufgerufen wird, während das Programm 
läuft. Das führt dann dazu, dass der Speicher(bei new "heap" genannt) 
irgendwann schlicht alle ist. Ein weiteres Problem ist, dass der 
Speicher früher alle sein kann als gedacht. Das liegt an der 
Fragmentierung.

Zu deinem Problem: Das könnte man mit Copy Initialisierung lösen, wenn 
das für dich eine Option ist.
1
struct OutputPin {
2
  OutputPin() = default;
3
  OutputPin(int pin, int port) {}
4
};
5
struct InputPin {
6
  InputPin() = default;
7
  InputPin(int pin, int port) {}
8
};
9
10
class Uart {
11
 public:
12
  Uart(int instanz) {
13
    switch (instanz) {
14
      case 1:
15
        TxPin = OutputPin(1, 1);
16
        RxPin = InputPin(1, 2);
17
    }
18
  }
19
20
 private:
21
  OutputPin TxPin;  // Memberobjekte
22
  InputPin RxPin;
23
};
24
25
int main() { Uart uart(1); }

von A. G. (grtu)


Lesenswert?

Richard schrieb:
> In Assembler. Denn es geht auch ganz ohne die künstlichen Probleme einer
> komplexen Sprach-Syntax.

Assembler? So ein neumodischer Schwachsinn. Wenn man direkt Binärcodes 
schreibt braucht man nur zwei Tasten!

von Theor (Gast)


Lesenswert?

@ Karsten

Die Empfehlungen 'new' nicht zu verwenden muss man, denke ich, mit etwas 
Salz geniessen.

Ich denke dabei an die Menschen, die seit über 20 Jahren malloc/free 
(new/delete sind im wesentlichen die modernen Analogien) verwenden und 
damit zurecht kommen.

Es ist zweifellos richtig, dass man in Programmen, die komplexe 
Kontrollflüsse haben, mit new/delete sehr sorgfältig arbeiten muss. Auch 
hat sich so Mancher das ein oder andere Mal Stunden bis Tage damit 
herumgeschlagen, Speicherlecks zu finden. Nicht umsonst gibt es einige 
Tools für diesen Fall.

Es ist aber, wie eben genau diese Vergangenheit zeigt, durchaus möglich 
korrekte Programme mit new/delete zu schreiben.

Ich selbst verstehe die Empfehlung so, dass ich, um mir die Arbeit 
leicht zu  machen, mich auf Konstruktoren/Destruktoren in einem gewissen 
Maß verlasse, aber auch immer damit rechne, dass was schiefgehen kann.

Murphy lebt!

Dennoch verwende ich new/delete immer dann, wenn es mir angebracht 
erscheint.

Einzig ein Anfänger bzw. jemand der das noch nie verwendet hat, sollte 
gewarnt sein, dass es Probleme geben kann und das die Suche nach dem 
Fehler oft sehr mühsam ist.

Die Warnung ist jedenfalls nicht absolut zu sehen. Wenn es Dir richtig 
erscheint und anders nur noch schwieriger wird, würde ich die Verwendung 
durchaus für erwägenswert erachten.

von Richard (Gast)


Lesenswert?

A. G. schrieb:
> Assembler? So ein neumodischer Schwachsinn. Wenn man direkt Binärcodes
> schreibt braucht man nur zwei Tasten!

Damit ist aber die nötige Kenntnis der vorhandenen CPU-Instruktionen 
nicht ersetzt. Zum einfach Merken/Programmieren- Können der 0/1er Wüste 
gibts die wenigen entsprechenden Asm-Wörter. Zu notieren auf einem A4 
Blatt statt in einem 1000 seitigen Wälzer. Clever, was?

von Einer K. (Gast)


Lesenswert?

Karsten D. schrieb:
> Wie löse ich mein Problem denn ohne new?
1
class Uart
2
{
3
    private:
4
    OutputPin TxPin;  //Memberobjekte
5
    InputPin  RxPin;
6
7
    public:
8
    // Pins per copy 
9
    Uart(OutputPin TxPin,InputPin RxPin):TxPin(TxPin),RxPin(RxPin){}
10
};
11
12
Uart test{OutputPin{1,1},InputPin{2,2}}; // Instanziierung

Verbesserung:
Evtl. etwas mehr Referenzen nutzen, als auf Copy setzen

von A. G. (grtu)


Lesenswert?

Bezieht sich die Warnung vor new/delete nicht vor allem auf Fälle in 
denen es nicht absehbar ist in welchem Umfang zur Laufzeit Speicher 
allokiert wird, oder wo ständig verschieden große Objekte erzeugt und 
gelöscht werden?

Richard schrieb:
> Damit ist aber die nötige Kenntnis der vorhandenen CPU-Instruktionen
> nicht ersetzt. Zum einfach Merken/Programmieren- Können der 0/1er Wüste
> gibts die wenigen entsprechenden Asm-Wörter. Zu notieren auf einem A4
> Blatt statt in einem 1000 seitigen Wälzer. Clever, was?

Ich denke, dass es interessant ist mal ein paar Sachen in Assembler 
gemacht zuhaben um zu wissen was der Prozessor im Hintergrund eigentlich 
macht. Aber heutzutage ist dieses Micro-Management doch in fast allen 
Fällen weder notwendig noch sinnvoll. Aber so weit wollte ich garnicht 
vom Thema abschweifen, zu dem ich nichtmal irgendwas sinnvolles 
beigetragen habe (schäm).

von nfet (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Uart test{OutputPin{1,1},InputPin{2,2}}; // Instanziierung

Damit müssten man das Wissen darüber, welcher Uart welchen Pin benötigt 
von außen mitbringen. Nicht sehr elegant.

von Einer K. (Gast)


Lesenswert?

nfet schrieb:
> Damit müssten man das Wissen darüber, welcher Uart welchen Pin benötigt
> von außen mitbringen.
Constructor Injection
Ein übliches Verfahren...

Offensichtlich soll die Information irgendwie in die Klasse rein....
Alternativen wird es geben.
Der Fakt: Injection muss sein!
Egal wie.

Und new ist unerwünscht.
So geht es ohne.



nfet schrieb:
> Nicht sehr elegant.
Fest verdrahtet in der Klasse ist doch auch doof.

von Theor (Gast)


Lesenswert?

A. G. schrieb:
> Bezieht sich die Warnung vor new/delete nicht vor allem auf Fälle in
> denen es nicht absehbar ist in welchem Umfang zur Laufzeit Speicher
> allokiert wird, oder wo ständig verschieden große Objekte erzeugt und
> gelöscht werden?
>
> [...]

Wenn ich auf diese Frage eingehen darf:

Es kommt darauf an, was genau man als "nicht absehbar" betrachtet und 
was das für Fälle sind.

Programme an sich werden im Grunde mit dem Ziel geschrieben, dass sich 
ein deterministischer Ablauf ergibt. Wenn das erreicht und durch Tests 
geprüft wird, dann existieren solche Fälle nicht. Also ist auch absehbar 
in welchem Umfang in welcher Situation Speicher alloziiert wird.

Andere Fälle sind demzufolge Programme, die nicht deterministisch 
ablaufen. Oder solche, in denen man leider nicht jeden möglichen Fall 
berücksichtigt hat; was man beim Test feststellen kann.

von nfet (Gast)


Lesenswert?

Durchaus ein übliches Verfahren, aber wenn man das Interface von Uart 
beibehalten will, wofür es ja gute Gründe gibt eben eher ungeeinget.

Denn wenn ich jetzt "Uart uart(UART1);" schreibe oder "Uart 
uart(OutputPin{1,1},InputPin{2,2})" dann würde ich doch das erste 
definitv bevorzugen.

Man kann sich natürlich auch irgendwelche Builder drum herum schreiben, 
um das zu verhindern, aber wann da dann der default constructor 
aufgerufen wird und wann  nicht, da bin ich mir nie ganz sicher..
1
Uart MakeUart(int instanz) {
2
    switch (instanz) {
3
        case 1: return Uart{OutputPin(1,1), InputPin(2,2)};
4
    }
5
};
6
Uart test = MakeUart(1);

von Wilhelm M. (wimalopaan)


Lesenswert?

Karsten D. schrieb:
> Uart(instanz);

Lerne erst mal C++ außerhalb eines µC!

von Stefan F. (Gast)


Lesenswert?

nfet schrieb:
> Was gegen new spricht: es ist leicht falsch zu verwenden.

Ehrlich gesagt verstehe ich nicht, warum das immer wieder geschrieben 
wird, als ob es der heilige Gral wäre. Sind denn alle Programmierer zu 
blöd, new und delete paarweise Einzusetzen? Kann ich mir nicht 
vorstellen.

Messer sind auch leicht falsch zu verwenden, dennoch nimmt das kein 
normaler Mensch zum Anlass, auf die Benutzung von Messern zu verzichten.

Wahrscheinlich ist das eher so ein psychologisches Ding: Was alle sagen, 
muss gut sein. Maggie macht die einzig guten Miracolis, nur Coca Cola 
ist richtige Cola und Pointer sind böse. Schön wär's, doch so einfach 
wird man nicht ein guter Fachmann.

Wer mit Pointern nicht zurecht kommt oder sich vor ihnen fürchtet, der 
hat sicher auch weitere gravierende Schwächen in der 
Software-Entwicklung.

von Rolf M. (rmagnus)


Lesenswert?

Ich würde irgendwas so grob in der Richtung machen:
1
class Uart
2
{
3
privte:
4
    enum UART
5
    {
6
        UART1 = 0,
7
        UART2 = 1
8
    };
9
10
    static constexpr struct
11
    {
12
         Port inPort;
13
         Pin  inPin;
14
         Port outPort;
15
         Pin  outPin;
16
    }
17
    pins[] = 
18
    {
19
        { PortA, Pin5, PortA, Pin3 },
20
        { PortC, Pin3, PortD, Pin1 }
21
    };
22
23
public:
24
    Uart(UART instanz)
25
        : OutputPin { pins[instanz].outPort, pins[instanz].outPin },
26
          InputPin  { pins[instanz].inPort, pins[instanz].inPin }
27
    {
28
    }
29
30
private:
31
    OutputPin TxPin;  //Memberobjekte
32
    InputPin RxPin;
33
};
34
35
// ...
36
Uart(Uart::UART2) uart3;

von Stefan F. (Gast)


Lesenswert?

Theor schrieb:
> Es ist aber, wie eben genau diese Vergangenheit zeigt, durchaus möglich
> korrekte Programme mit new/delete zu schreiben.

Eben. Und es ist genau so möglich, ohne new/delete fehlerhafte Programme 
zu schreiben. Sogar mit dem gefühlt zwanzigsten verbesserten Garbage 
Collector (siehe Java).

von Stefan F. (Gast)


Lesenswert?

Richard schrieb:
> Zu notieren auf einem A4
> Blatt statt in einem 1000 seitigen Wälzer. Clever, was?

Wenn du zu jedem ASM Befehl aufschreibst, was er bewirkt, dann kommst du 
mit einem A4 Blatt aber nicht weit. Die Doku des Befehlssatzes vom 
SAB80535 umfasste zum Beispiel schon weit über 100 Seiten.

von Stefan F. (Gast)


Lesenswert?

Rolf M. schrieb:
> Ich würde irgendwas so grob in der Richtung machen:

Gefällt mir!

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Ich würde irgendwas so grob in der Richtung machen:

Und warum möchte man mehr als einmal ein UART-Objekt mit derselben 
Instanznummer(?) erzeugen können?

von Oliver S. (oliverso)


Lesenswert?

Eigentlich ist das Universalrezelt, wenn mna nicht weiter kommt, einen 
weiterel Level Indirection einzufügen.

In dem Fall eine Klasse/Funktion, die eine Instanz als Patameter nimmt, 
und damit die passenden Pins setzt.

Oliver

von Rolf M. (rmagnus)


Lesenswert?

Wilhelm M. schrieb:
> Und warum möchte man mehr als einmal ein UART-Objekt mit derselben
> Instanznummer(?) erzeugen können?

Das musst du den TE fragen. Ich hab nur die Schnittstelle so umgesetzt, 
wie er sie vorgegeben hat. Wie lautet denn dein Gegenvorschlag?

von Wilhelm M. (wimalopaan)


Lesenswert?

Rolf M. schrieb:
> Wie lautet denn dein Gegenvorschlag?

Die Instanz-ID (Nummer, Typ, whatever, ...) als template-parameter und 
voll-statische Klasse, d.h. konkreter Typ nicht-instanziierbar. Denn 
HW-Ressourcen sind in einem µC immer nur in endlicher, zur compile-zeit 
bekannter Anzahl vorhanden. Eine Laufzeit-Instanziierung für 
HW-Abstraktionen ist daher falsch. Daher nur conmpile-zeit 
Instanziierung (template Instanziierung).

von Mike R. (thesealion)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ehrlich gesagt verstehe ich nicht, warum das immer wieder geschrieben
> wird, als ob es der heilige Gral wäre. Sind denn alle Programmierer zu
> blöd, new und delete paarweise Einzusetzen? Kann ich mir nicht
> vorstellen.

Wahrscheinlich weil ihnen jahrelang gepredigt wurde, dass dynamische 
Speicherverwaltung auf einem µC "böse" ist.

Ich setze seit einiger Zeit ohne Bedenken sogar "malloc" ohne "free" auf 
einem µC ein. (Gerade so eine USART ist ein tolles beispiel dafür, die 
Klasse/das Modul kann man beim Start des Programms erzeugen und so lange 
der µC läuft macht es (normalerweise) keinen Sinn das ganze wieder zu 
löschen, da die Hardware ja so bleibt wie sie ist.)

von A. B. (Gast)


Angehängte Dateien:

Lesenswert?

Eine Library, die genau so etwas zur Verfügung stellt, findet sich hier:

https://github.com/KonstantinChizhov/Mcucpp
*******************************************
mit Updates c++17 03-2020

../mcucpp/ARM/Stm32F40x/usart.h
   ****************************

von Karsten D. (kenny7537)


Lesenswert?

Hallo zusammen,

erst einmal vielen Dank für die vielen Antworten!

Das mit den Speicherlecks und new hatte ich zuvor schon aufgeschnappt, 
wie einige auch schon richtig bemerkt haben, ist das bei einem Objekt 
wie dem UART und den Pins, die nur einmal erzeugt und dann nicht weiter 
verändert werden glaube ich kein größeres Problem?
Ich hatte nur andererseits gelesen, dass der Compiler dann weniger 
Möglichkeiten zu optimieren hat und dass die dynamische 
Speicherverwaltung weitere Ressourcen verbraucht. Ist das nicht korrekt?

Die folgenden Themen werde ich mir auf jeden Fall mal ansehen:
- Copy initialisierung (Danke an nfet)
- Constructor Injection (Arduino Fanboy D.)

Der Ansatz von Rolf M. ist auch spannend, danke dafür!

Wilhelm M. schrieb:
> Rolf M. schrieb:
>> Wie lautet denn dein Gegenvorschlag?
>
> Die Instanz-ID (Nummer, Typ, whatever, ...) als template-parameter und
> voll-statische Klasse, d.h. konkreter Typ nicht-instanziierbar. Denn
> HW-Ressourcen sind in einem µC immer nur in endlicher, zur compile-zeit
> bekannter Anzahl vorhanden. Eine Laufzeit-Instanziierung für
> HW-Abstraktionen ist daher falsch. Daher nur conmpile-zeit
> Instanziierung (template Instanziierung).

Grundsätzlich erscheint mir das hier richtig, die ganze 
Hardware-Initialisierung verändert sich nicht mehr und der Compiler kann 
das gerne für mich alles fertig machen.
Jetzt muss ich nur noch herausfinden, wie das konkret geht...
Das von A. B. (Danke!) verlinkte Codebeispiel scheint auch in diese 
Richtung zu gehen, das muss ich mir aber nochmal in Ruhe ansehen.

Ich danke auch allen, die ich jetzt nicht namentlich aufgeführt habe für 
die konstruktive Diskussion und freue mich natürlich, wenn jemandem noch 
weitere Aspekte oder Ansätze einfallen!

Viele Grüße
Karsten

von Stefan F. (Gast)


Lesenswert?

Karsten D. schrieb:
> Ich hatte nur andererseits gelesen, dass der Compiler dann weniger
> Möglichkeiten zu optimieren hat und dass die dynamische
> Speicherverwaltung weitere Ressourcen verbraucht. Ist das nicht korrekt?

Ja das ist Korrekt. ABer solange du beim Start nur eine Hand voll 
Objekte erzeugst und die dann auch immer im Speicher bleiben, ist der 
Aufwand sehr gering.

von Johannes S. (Gast)


Lesenswert?

der F446 hat ja 128 kB RAM, da wird man doch nicht um jedes Byte kämpfen 
müssen und kann etwas in Komfort investieren.
Ein uart1 mit fixer Belegung eines Pins spiegelt nicht die richtige 
Hardware wieder, es gibt bei den uart auch alternative Pins. Oder gar 
eine Switchmatrix wo die Funktion auf fast jeden Pin gelegt werden kann 
(allerdings nicht bei den F4). Von daher ist eine Initialisierung mit 
uart(PA_9, PA_10) gar nicht so verkehrt. Bei Mbed ist das auch so, 
intern gibt es dann MCU Abhängig Pinmaps die nach gültigen Kombinationen 
durchsucht werden.

von (Gast)


Lesenswert?

Karsten D. schrieb:
>>> Wie lautet denn dein Gegenvorschlag?
>>
>> Die Instanz-ID (Nummer, Typ, whatever, ...) als template-parameter und
>> voll-statische Klasse, d.h. konkreter Typ nicht-instanziierbar. Denn
>> HW-Ressourcen sind in einem µC immer nur in endlicher, zur compile-zeit
>> bekannter Anzahl vorhanden. Eine Laufzeit-Instanziierung für
>> HW-Abstraktionen ist daher falsch. Daher nur conmpile-zeit
>> Instanziierung (template Instanziierung).
>
> Grundsätzlich erscheint mir das hier richtig, die ganze
> Hardware-Initialisierung verändert sich nicht mehr und der Compiler kann
> das gerne für mich alles fertig machen.
> Jetzt muss ich nur noch herausfinden, wie das konkret geht...
> Das von A. B. (Danke!) verlinkte Codebeispiel scheint auch in diese
> Richtung zu gehen, das muss ich mir aber nochmal in Ruhe ansehen.

Ich denke man sollte sich vorher im Klaren sein, was man eigentlich 
will/braucht.

Unter Umständen will man die HW ja zur Laufzeit recht dynamisch 
konfigurieren können, da kanns schon sein, dass ein Pin entweder als 
UART-RX genutzt wird oder als GPIO.

Wenns z.B. Funktionen geben soll, die als Parameter eines von mehreren 
UART-Objekten verwenden können sollen, wird man mit der statischen 
template-Version wahrscheinlich auch nicht glücklich werden.

Wenn aber alles halbwegs fix ist, was vermutlich bei der überwiegenden 
Mehrzahl an µC-Anwendungen so ist, kann man mit den template-Klassen 
Laufzeitoverhead verringern, schon im Code falsche Verwendung verhindern 
etc..., eventuell aber auf Kosten der Programmgröße und "debugability".

Ich hab das in etwa so gelöst:
1
namespace sowieso {
2
template <>
3
struct USART<3> {
4
  struct Pins {
5
    using PB0  = AlternateFunctionPin<1,  0,  7>;
6
    using PB12 = AlternateFunctionPin<1, 12,  7>;
7
    // etc...
8
  };
9
10
  using RX_Pins      = std::tuple<Pins::PB11, Pins::PC11, Pins::PC5, Pins::PD9>;
11
  using TX_Pins      = // etc...
12
};
13
14
}
15
16
template <int n>
17
struct USARTParams
18
  : public sowieso::USART<n>, // ...
19
{
20
//...
21
};
22
23
template <int n,
24
          typename Params = USARTParams<n> >
25
struct USART
26
  : public Params
27
{
28
  static void init() {...}
29
  static void deinit() {...}
30
  static void set_baudrate(uint32_t baudrate) {...}
31
  // etc
32
};

Über die Params bekommt das USART-Template auch noch generierte 
Register-Definitionen und diverse andere Parameter wie verwendbare 
DMA-Kanäle, IRQ, Clock etc...

verwendet wird das dann so
1
struct hw {
2
  using console = nmspc::USART<1>;
3
  using console_rx = nmspc::Pins::PA10;
4
  using console_tx = nmspc::Pins::PA9;
5
6
  static void init()
7
  {
8
    console::init();
9
    console_rx::init();
10
    console_tx::init();
11
    // ...
12
  }
13
};
14
15
16
void main()
17
{
18
  hw::init();
19
20
  hw::console::send_string_dma("hello world");
21
}

von Wilhelm M. (wimalopaan)


Lesenswert?

Johannes S. schrieb:
> der F446 hat ja 128 kB RAM, da wird man doch nicht um jedes Byte kämpfen
> müssen und kann etwas in Komfort investieren.

Darum geht es doch gar nicht.

Johannes S. schrieb:
> Oder gar
> eine Switchmatrix wo die Funktion auf fast jeden Pin gelegt werden kann
> (allerdings nicht bei den F4).

Das kann man alles wunderbar zur Compilezeit(!) machen. Und auch 
abhängig vom MCU-Type und seinen Eigenschaften.

von Wilhelm M. (wimalopaan)


Lesenswert?

rµ schrieb:
> Wenns z.B. Funktionen geben soll, die als Parameter eines von mehreren
> UART-Objekten verwenden können sollen, wird man mit der statischen
> template-Version wahrscheinlich auch nicht glücklich werden.

Warum nicht?

rµ schrieb:
> Wenn aber alles halbwegs fix ist, was vermutlich bei der überwiegenden
> Mehrzahl an µC-Anwendungen so ist, kann man mit den template-Klassen
> Laufzeitoverhead verringern, schon im Code falsche Verwendung verhindern
> etc..., eventuell aber auf Kosten der Programmgröße und "debugability".

Man ist bestrebt Code zu haben, der, wenn er semantisch falsch ist, 
nicht compiliert. Und das erreicht man nur, indem man wie oben schon 
gesagt, möglichst viel zur Compilezeit macht.

rµ schrieb:
> Ich hab das in etwa so gelöst:

Ja, genau! Das geht in die richtige Richtung. Ich weiß nicht, wie oft 
ich das hier schon in den letzten Jahren geschrieben habe, aber Du bist 
wohl der erste, der das richtig verstanden hat. Glückwunsch!!! Freut 
mich ;-)

von (Gast)


Lesenswert?

Wilhelm M. schrieb:
> rµ schrieb:
>> Wenns z.B. Funktionen geben soll, die als Parameter eines von mehreren
>> UART-Objekten verwenden können sollen, wird man mit der statischen
>> template-Version wahrscheinlich auch nicht glücklich werden.
>
> Warum nicht?

Eine Instanz kann ich als Parameter übergeben, eventuell auch eine 
Referenz/Pointer davon machen und mir merken. Mit der statischen 
template-Variante ginge das nur über gemeinsame Basisklasse und einem 
Wald an virtuellen Funktionen oder "statischem Polymorphismus" den man 
sich selber stricken muss, aber das ist eine andere Dose Würmer ;-)

Wenn man zur Laufzeit nichts umkonfigurieren muss dann soll man zur 
Compilezeit erledigen was geht, total d'accord.

von Vincent H. (vinci)


Lesenswert?

Wilhelm M. schrieb:
> Ja, genau! Das geht in die richtige Richtung. Ich weiß nicht, wie oft
> ich das hier schon in den letzten Jahren geschrieben habe, aber Du bist
> wohl der erste, der das richtig verstanden hat. Glückwunsch!!! Freut
> mich ;-)

Ich verwende bei meinem Arbeitgeber eine STM32L4 Bibliothek die sehr 
ähnlich aussieht. Für mich persönlich ist der größte Pluspunkt, dass man 
Hardware-Abhängigkeiten (insbesondere zu Interrupt-Vektoren und 
DMA-Kanälen) sehr gut darstellen kann. Das erlaubt mir bei verschiedenen 
Produkten je nach Pin-Belegung verschiedene Schnittstellen zu benutzen 
ohne das ich mir Gedanken darüber machen muss welcher DMA Kanal da jetzt 
wofür gut ist.

Ich lege dazu für den Prozessor eine Art Device-Tree an der alle 
Peripherien enthält. Das kann man sich etwa so vorstellen, hier mit 
einer I2C Schnittstelle als Beispiel:
1
struct Peripheral_t {
2
// Beispiel I2C
3
  struct I2c2_t {
4
    enum class IRQn { Event = I2C2_EV_IRQn, Error = I2C2_ER_IRQn };
5
    struct DmaRequest1Rx_t : Dma1_t::Channel5_t {
6
      static constexpr auto number{3u};
7
    };
8
    struct DmaRequest1Tx_t : Dma1_t::Channel4_t {
9
      static constexpr auto number{3u};
10
    };
11
    static constexpr I2c2Map* reg{};
12
  };
13
14
// ... usw.
15
};

Diesen Typen fütter ich dann in eine I2C Klasse, die entsprechende 
(statische) Methoden zum senden/lesen/etc. hat.

Möchte ich zwischen zwei I2C Schnittstellen umschalten so genügt ein:
1
using EepromI2c = I2c<I2c1_t>;
2
// using EepromI2c = I2c<I2c2_t>;


rµ schrieb:
> Eine Instanz kann ich als Parameter übergeben, eventuell auch eine
> Referenz/Pointer davon machen und mir merken. Mit der statischen
> template-Variante ginge das nur über gemeinsame Basisklasse und einem
> Wald an virtuellen Funktionen oder "statischem Polymorphismus" den man
> sich selber stricken muss, aber das ist eine andere Dose Würmer ;-)

Du kannst statt einer Referenz oder einem Pointer aber auch einfach 
einen Typen übergeben:
1
template<typename T>

Egal ob bei Klassen oder Funktionen.

Zugegeben, dass der ganze Code dann in Headern lebt ist bei Problemen 
die man wirklich nur an der Hardware debuggen kann manchmal schon 
mühsam. Aber das ist ein Trade-Off mit dem ich Leben kann.

von (Gast)


Lesenswert?

Vincent H. schrieb:
> Du kannst statt einer Referenz oder einem Pointer aber auch einfach
> einen Typen übergeben:template<typename T>
>
> Egal ob bei Klassen oder Funktionen.

Das ist schon klar, wenn ich aber eine Factory habe, die Abhängig von 
einer Laufzeit-Konfigurations-Datei einen UART1 oder UART2 macht und ich 
die dann wohin übergeben will nützt das nichts.

von Karsten D. (kenny7537)


Lesenswert?

Wow, vielen Dank!

@rµ: Jetzt hast du mir was zu knabbern gegeben, das wird ein bisschen 
dauern, bis ich durch die Syntax und die Funktionsweise der Templates 
gestiegen bin...

Aber zum Glück gibt es ja google :)

Hat es eigentlich einen Grund, dass ihr so viel mit structs und selten 
mit Klassen arbeitet?

Viele Grüße
Karsten

von Einer K. (Gast)


Lesenswert?

Karsten D. schrieb:
> Hat es eigentlich einen Grund, dass ihr so viel mit structs und selten
> mit Klassen arbeitet?

Gibt leichte Unterschiede bei den Sichtbarkeitsregeln, ist aber sonst 
egal.

Also verwendet man das, wo es weniger zu tippen gibt.
Faulheit.

Alternatives Argument:
Keine Methoden, dann struct.

von Asm'ler (Gast)


Lesenswert?

Karsten D. schrieb:
> @rµ: Jetzt hast du mir was zu knabbern gegeben, das wird ein bisschen
> dauern, bis ich durch die Syntax und die Funktionsweise der Templates
> gestiegen bin...

Das geschieht Euch ganz recht.
Je komplizierter die Sprachbürokratie desto besser!
Der Vernunftbegabte steuert seine UART direkt an und gut ist. Natürlich 
mit der einfachsten MCU die für den Zweck reicht. Aber wenn man halt 
nach unötigen Komplexitäten dürstet nur zu! Verliert Euch im Absurden! 
Immerhin lässt sich dann hier trefflich streiten- wie man ja bei solchen 
abstrakten Wolkenkuckuckssprachen immer wieder sieht.

von Einer K. (Gast)


Lesenswert?

Asm'ler schrieb:
> Verliert Euch im Absurden!

Es gibt 3 Wege:

Der Goldene
Problem analysieren, Lösung erarbeiten, Lösung durchsetzen

Der Silberne
Genau so machen, wie die anderen, abschauen.

Der Bronzene
Aus Erfahrung lernen.


Alle anderen Wege führen ins Versagen.

von M.K. B. (mkbit)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ehrlich gesagt verstehe ich nicht, warum das immer wieder geschrieben
> wird, als ob es der heilige Gral wäre. Sind denn alle Programmierer zu
> blöd, new und delete paarweise Einzusetzen? Kann ich mir nicht
> vorstellen.

Ja, wenn der Kontrollfluss einfach ist, dann kann man das Paarweise 
schreiben.
Mit Exceptions kann es jedoch sein, dass man unterwegs aussteigt, ohne 
am delete vorbeizukommen.

Mit unqiue_ptr ist sichergestellt, dass beim Verlassen des Scopes 
zerstört wird. Egal ob durch Exception oder ein Return, was sich 
irgendwo eingeschlichen hat. Auch wenn man den Pointer doppelt zuweisen 
sollte entsteht kein Speicherleck.

von M.K. B. (mkbit)


Lesenswert?

Karsten D. schrieb:
> Hier muss ich mich aber zuvor entscheiden, wie die Pins konfiguriert
> werden sollen, was ich ja aus offensichtlichen Gründen nicht kann.

Du könntest auch zwei static Methoden in der Klasse anlegen, die dir den 
Pin für Tx und RX zurückgeben. In der Initialisierung rufst du die 
Methode dann mit den Parametern auf.
1
Uart:getTxPin(instanz)
2
{
3
  //switch
4
}
5
6
Uart::Uart(instanz) : TxPin(portA, getTxPin(instanz)), RxPin(portA, getRxPin(instanz))
7
{
8
  //usw...
9
}

von Asm'ler (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Es gibt 3 Wege:
>
> Der Goldene
> Problem analysieren, Lösung erarbeiten, Lösung durchsetzen

Merkt Ihr nicht, wie Ihr hier nur Zeit für die Probleme und das 
Verständnis einer Sprachbürokratie verschleudert statt das eigentliche 
Problem anzugehen? Schaut doch bitte mal statt in holden Elfenbeinturm 
Hoch-und Höchstsprach- Lehrbücher lieber ins Datenblatt Eurer Controller 
und findet dort die beste Lösung! Die dürfte (womöglich in Gestalt 
bereits vorhandener Hardware-Features) wesentlich eleganter und 
effizienter sein! Das und nur das ist der richtige Weg!

von Wilhelm M. (wimalopaan)


Lesenswert?

Asm'ler schrieb:
> Merkt Ihr nicht, wie Ihr hier nur Zeit für die Probleme und das
> Verständnis einer Sprachbürokratie verschleudert statt das eigentliche
> Problem anzugehen?

Hat c-hater sich umbenannt?

von Einer K. (Gast)


Lesenswert?

Ach, Priester, die ihre eigenen Beschränkungen als alleinig selig 
machende Heilslehre verkaufen wollen, gibts genug.

Immerhin ist das Asm'ler Kerlchen freundlich dabei.
Das schafft auch nicht jeder Priester.

von (Gast)


Lesenswert?

Asm'ler schrieb:
> Merkt Ihr nicht, wie Ihr hier nur Zeit für die Probleme und das
> Verständnis einer Sprachbürokratie verschleudert statt das eigentliche
> Problem anzugehen? Schaut doch bitte mal statt in holden Elfenbeinturm
> Hoch-und Höchstsprach- Lehrbücher lieber ins Datenblatt Eurer Controller
> und findet dort die beste Lösung!

Nicht alles, was man auf so einem µC machen will hat direkt mit der 
Hardware zu tun. Signalverabeitung, Filter, etc... Sowas will man auch 
nicht in Assembler programmieren.

Gerade im Bereich von DSP bietet sich C++ an, da man Algorithmen recht 
allgemein als templates formulieren kann, und dann mit konkreten 
Datentypen je nach Anwendung (double, float, fixkomma, integer) 
verwenden kann ohne immer alles neu zu schreiben.

In Assembler würde man da immer bei 0 wieder anfangen. Abgesehen davon 
ist Assembler auf den ARMs auch nicht mehr so einfach und übersichtlich 
wie zu Z80-Zeiten. Schon alleine einen Überblick über die 
Registerinhalte zu behalten stell ich mir schwierig vor. Zwangsweise 
muss man sich da selber einschränken, was erst recht wieder auf 
suboptimalen Code und C- oder Pascal-artige Aufrufkonventionen 
hinausläuft.

Heutzutage darf man nicht vernachlässigen wie effizient ein Programm 
erstellt werden kann, wie wartbar das ist was herauskommt. Assembler ist 
da eher nicht bei den besten Kandidaten.

von Hans B. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Ach, Priester, die ihre eigenen Beschränkungen als alleinig selig
> machende Heilslehre verkaufen wollen, gibts genug.

Die Beschränkung besteht im sinnlosen Verbraten von Zeit für 
Kunstprobleme, die mit der Sache nichts mehr zu tun haben.

rµ schrieb:
> Sowas will man auch
> nicht in Assembler programmieren.

Davon war auch nie die Rede.

> Gerade im Bereich von DSP bietet sich C++ an, da man Algorithmen recht
> allgemein als templates formulieren kann, und dann mit konkreten
> Datentypen je nach Anwendung (double, float, fixkomma, integer)
> verwenden kann ohne immer alles neu zu schreiben.

Man wird immer diese oder jene Begründung finden können warum nun 
dieses oder jenes Sprachkonstrukt genau hier und dort hilfreich wäre. 
Das ist ja gerade der Grund der solche Sprachen immer weiter aufbläht. 
Der Betriebsblinde sieht aber das Ganze nicht mehr: Zunehmende 
Abstraktheit und Komplexität fordern ihren Tribut. Sie verlagern Zeit 
und Mühe nur an andere Stellen. Letztlich und nüchtern betrachtet in 
immer mehr intellektuellen Overhead den die eigentlichen Anwendung gar 
nicht braucht. Den immer weniger Leute noch lernen, durchschauen und 
noch viel weniger perfekt beherrschen! Das ist auch das Gegenteil von 
wartbar und fehlerminimierend. Keep it simple wäre eigentlich auch hier 
das Motto, aber das ist Leuten, die nach Komplexität und Intransparenz 
(in Abgrenzung von anderen?) lechzen natürlich schwer vermittelbar...

von Max (Gast)


Lesenswert?

Ich werden gegen wissen, wie die, die C++ gewerblich nutzen und damit 
sofort 61508/26262/50128 oder sogar 178C erfüllen müssen, das alles 
zertifizieren und hauptsächlich testen werden. Und dazu kommt ein 
Compiler mit Pre- Qualification, nicht Rede über Prozesse wie (A)SPICE 
usw. Es ist nicht böse gemeint, ich werde es gerne wissen.

von nfet (Gast)


Lesenswert?

Max schrieb:
> Ich werden gegen wissen, wie die, die C++ gewerblich nutzen und damit
> sofort 61508 ...

Wie sonst auch? Wo soll der Unterschied sein? Für so lustige Dinge wie 
misra gibt's natürlich auch c++ Äquivalente und auch zertifizierte 
compiler gibt es natürlich.
Inwiefern das irgendeine Sicherheit erhöht...

Ich für meinen Teil würde auf jeden Fall eher einer von einem aktuellen 
GCC kompilierten Software vertrauen, als einen zertifizierten Compiler 
mit einem GCC von vor 10 Jahren, der via proven in use einen TÜV Stempel 
hat.
Und lesbarer Code ist mir auch lieber als misra konformer.

von Rolf M. (rmagnus)


Lesenswert?

nfet schrieb:
> Ich für meinen Teil würde auf jeden Fall eher einer von einem aktuellen
> GCC kompilierten Software vertrauen, als einen zertifizierten Compiler
> mit einem GCC von vor 10 Jahren, der via proven in use einen TÜV Stempel
> hat.
> Und lesbarer Code ist mir auch lieber als misra konformer.

Aber wenn die Vorgabe nun mal ist, dass der Compiler zertifiziert sein 
muss und der Code MISRAten, dann muss man damit leben, auch wenn's einem 
nicht lieb ist.

von Karsten D. (kenny7537)


Lesenswert?

Guten Abend,

da ich diesen Thread ins Leben gerufen habe,  möchte ich mich noch 
einmal kurz zu Wort melden:

Ich habe inzwischen ein wenig mit der Templateprogrammierung 
herumgespielt, aber mich auch mit den anderen Ansätzen befasst. Es wird 
vermutlich noch etwas dauern, bis ich meinen eigenen Weg gefunden habe, 
allerdings hat mir allein das Aufzeigen der verschiedenen Möglichkeiten 
sehr weitergeholfen.
Denn wie soll man über etwas nachdenken, von dem man nichts weiß?

Ich bedanke mich bei allen, die sich beteiligt haben für die wertvollen 
Hinweise und wünsche euch noch einen schönen Abend und frohe Ostern

Viele Grüße
Karsten

PS: Ich weiß, dass dieser Beitrag inhaltlich nicht besonders gehaltvoll 
war, aber komplett kommentarlos wollte ich mich aber auch nicht 
verdrücken ;)

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.