Forum: Mikrocontroller und Digitale Elektronik Methode in C++ Initializer List aufrufen


von Vereinfacher (Gast)


Lesenswert?

Ich programmiere einen STM32 in C++. Als erstes soll die Hardware 
initialisiert werden. Damit diese Methode vor der Initialisierung aller 
anderen Objekte meiner Initializer Liste ausgeführt wird, habe ich diese 
bis jetzt in eine eigene Klasse gepackt nur mit dieser einen Methode als 
Konstruktor. Das sieht dann so aus:
1
class Hardware {
2
  public:
3
    Hardware();
4
};
5
6
Hardware::Hardware() {
7
}
8
9
class MeinProjekt{
10
  public:
11
    MeinProjekt();
12
  private:
13
    Hardware meinHardware;
14
    SPI meinSPI;
15
};
16
17
MeinProjekt::MeinProjekt() : meinHardware(), meinSPI(SPI1, 1){
18
}

Kann ich die Methode Hardware::Hardware() irgendwie in MeinProjekt 
integrieren und diese als erstes ausführen lassen?

von Thomas W. (goaty)


Lesenswert?

Schön dass du Klassen magst, aber hier brauchst du doch gar keine.
Mach einfach die Initialisierung und dann den Rest und gut ist.

Ansonsten macht es das doch schon so wie du willst. Als erstes wird der 
Konstruktor von Hardware gerufen.

von Vereinfacher (Gast)


Lesenswert?

Thomas W. schrieb:
> Schön dass du Klassen magst, aber hier brauchst du doch gar keine.
> Mach einfach die Initialisierung und dann den Rest und gut ist.

Und wie genau? Etliche Objekte haben keinen Defaultkonstruktor und 
müssen in der Initializer Liste stehen, benötigen aber zuvor einen 
Aufruf der Hardware() Methode.

Und nur damit diese vor den anderen Objekten ausgeführt wird, habe ich 
hier diese Methode in einen Konstruktor einer eigenen Klasse gemacht, 
die sonst nichts kann, außer ihren Kontruktor aufzurufen.

von Sven P. (Gast)


Lesenswert?

Vereinfacher schrieb:
> Kann ich die Methode Hardware::Hardware() irgendwie in MeinProjekt
> integrieren und diese als erstes ausführen lassen?
Du könntest die Methode halt auch in der Initializer-Liste aufrufen, und 
damit eine Dummy-Variable initialisieren...

Dein Problem ist aber irgendwie ein anderes. meinHardware und meinSPI 
passen nicht so richtig in dieselbe Liste. Irgendwie ist meinHardware 
eher eine Etage weiter oben.

Eventuell solltest du die Hardware-Klasse einfach beerben? Dann würde 
dein Aufruf einer Methode zum Aufruf des Konstruktors der Basisklasse:
1
class Hardware {
2
  public:
3
    Hardware();
4
};
5
6
Hardware::Hardware() {
7
}
8
9
class MeinProjekt: public Hardware{
10
  public:
11
    MeinProjekt();
12
  private:
13
    UART meinUART;
14
    SPI meinSPI;
15
    SONSTWAS meinSONSTWAS;
16
};
17
18
MeinProjekt::MeinProjekt() : Hardware(), meinUART(), meinSPI(SPI1, 1), meinSONSTWAS() {
19
}

von Sebastian (Gast)


Lesenswert?

Vereinfacher schrieb:
> Und wie genau? Etliche Objekte haben keinen Defaultkonstruktor und
> müssen in der Initializer Liste stehen, benötigen aber zuvor einen
> Aufruf der Hardware() Methode

Indem du die Funktion am Anfang von main aufrufst?

Aber so wie du es machst geht auch. Nur einfacher: Wenns die erste 
member variable ist braucht sie gar nicht in der Initializer Liste 
stehen und der Default ctor wird trotzdem zuerst aufgerufen. Du kannst 
die Klasse auch verschachtelt definieren. Oder einen Dummy member mit 
dem Returnwert einer static member function initialisieren.

von Thomas W. (goaty)


Lesenswert?

Funktionale Abhängigkeit sollte überhaupt nicht im Konstruktor 
abgebildet sein. Er sollte lediglich das Objekt initialisieren.
Eine nötige Reihenfolge damit abzubilden ist unübersichtlich.
Wenn, dann haben die Klassen ein init() das danach in einer bestimmten 
Reihenfolge gerufen wird.

von Vereinfacher (Gast)


Lesenswert?

Thomas W. schrieb:
> Wenn, dann haben die Klassen ein init() das danach in einer bestimmten
> Reihenfolge gerufen wird.

Soll ich also wieder auf C ausweichen, obwohl ich C++ nutze? Jedes 
meiner Objekte wie SPI, USART, etc. hat keinen defaultkonstruktor. 
Dadurch stelle ich sicher, dass alle diese Objekte beim Erstellen 
bereits initialisiert sein müssen: USART meineUSART(USART2, 9600);

Das ist auch weiter kein Problem und ich finde ich völlig schlüssig und 
auch ok, dass ich das über die Initializer Liste aufrufen muss. Durch 
die Reihenfolge werden auch die Abhängigkeiten untereinander 
sichergestellt. Nur dass ich Hardware() für einen Methodenaufruf extra 
als Hardware-Konstruktor anlegen muss finde ich eine unschöne Lösung.

Aber ok, scheinbar ist das schon er eleganteste Weg.

von Sven P. (Gast)


Lesenswert?

Vereinfacher schrieb:
> Aber ok, scheinbar ist das schon er eleganteste Weg.

Hast du überhaupt gelesen und/oder verstanden, was ich geschrieben habe?

von Vereinfacher (Gast)


Lesenswert?

Sven P. schrieb:
> Hast du überhaupt gelesen und/oder verstanden, was ich geschrieben habe?

Ja. In deinem Beispiel habe ich aber immernoch für diese eine Methode 
eine extra Klasse nur mit Konstruktor angelegt. Ob ich diese nun erbe 
oder als Objekt anlege und in die Initializer Liste packe ist doch egal?

Ich würde ja gerne Hardware() in meinProjekt als normale Methode 
integrieren und in der InitializerListe als erstes diese Methode 
ausführen lassen.

von Thomas W. (goaty)


Lesenswert?

Ich halte es für immer noch falsch das die Reihenfolge in der 
initialisierungsliste der Membervariablen eine Bedeutung hat. Das ist 
verwirrend und später nicht nachvollziehbar.
Wenn es Abhängigkeiten in der Reihenfolge gibt, also dass Hardware 
zuerst initialisiert werden muss, dann sollte das sichtbar sein und 
nicht in einer versteckten Reihenfolge in der initialisierungsliste.
Mit C vs C++ hat das erstmal nix zu tun, auch in c++ arbeitet ein 
Programm noch sequenziell.

von Vereinfacher (Gast)


Lesenswert?

Thomas W. schrieb:
> Mit C vs C++ hat das erstmal nix zu tun, auch in c++ arbeitet ein
> Programm noch sequenziell.

Aber wo ist der Unterschied, ob ich in der Initializer List "Hardware(), 
meinSPI(...), meinUSART(...), ..." schreibe oder im Konstruktor
1
Hardware();
2
meinSPI.init(...);
3
meinUSART.init(...);

Ich habe in beiden Fällen eine Reihenfolge vorgegeben. Sollte die 
Reihenfolge in der Initializer Liste nicht mit der Reihenfolge in der 
Klassendefinition übereinstimmen gibts sowieso eine Warnung, also alles 
paletti.

Vorteil Initializer Liste: Ich benötige keine Defaultkonstruktoren und 
kann mir das C-like .init() sparen.
Vorteil Kostruktor: Die Reihenfolge in der Klassendefinition ist egal.

Ich habe lieber einen eindeutigen Konstruktor (der erzwingt, dass die 
Objekte beim Erstellen bereits voll umfänglich initialisiert sind) und 
erspare mit das .init();

von Thomas W. (goaty)


Lesenswert?

Vereinfacher schrieb:
> Aber wo ist der Unterschied, ob ich in der Initializer List "Hardware(),
> meinSPI(...), meinUSART(...), ..." schreibe oder im Konstruktor
>
>
1
> Hardware();
2
> meinSPI.init(...);
3
> meinUSART.init(...);
4
>
>
> Ich habe in beiden Fällen eine Reihenfolge vorgegeben. Sollte die
> Reihenfolge in der Initializer Liste nicht mit der Reihenfolge in der
> Klassendefinition übereinstimmen gibts sowieso eine Warnung, also alles
> paletti.
Warnung gibt es nur wenn die Reihenfolge der Initialisierung nicht mit 
der Reihenfolge der Membervariablen in der Klasse übereinstimmt. Die 
Definitinsreihenfolge der Klassen ist egal. (Können in beliebiger 
Ordnung in anderen Modulen dazugelinkt werden)

> Vorteil Initializer Liste: Ich benötige keine Defaultkonstruktoren und
> kann mir das C-like .init() sparen.
> Vorteil Kostruktor: Die Reihenfolge in der Klassendefinition ist egal.
Defaultkonstruktor gibt es immer. Wenn du ihn nicht hinschreibst, macht 
es der Compiler automatisch.

> Ich habe lieber einen eindeutigen Konstruktor (der erzwingt, dass die
> Objekte beim Erstellen bereits voll umfänglich initialisiert sind) und
> erspare mit das .init();
Verstehe ich nicht. Du kannst das init von Hardware doch in dem 
Konstruktor, der Hardware verwendet und besitzt aufrufen.
Ich würde es so schreiben:

class Hardware {
  public:
    Hardware() {
      // here initialize Hardware
    }
};

class MeinProjekt{
  public:
    MeinProjekt();
  private:
    Hardware meinHardware;
    SPI meinSPI;
};

MeinProjekt::MeinProjekt()
{
  meinHardware(); // first initialize Hardware

  meinSPI(SPI1, 1);
}

von mh (Gast)


Lesenswert?

Thomas W. schrieb:
> Defaultkonstruktor gibt es immer. Wenn du ihn nicht hinschreibst, macht
> es der Compiler automatisch.

Das ist falsch.

Thomas W. schrieb:
>
1
> class Hardware {
2
>   public:
3
>     Hardware() {
4
>       // here initialize Hardware
5
>     }
6
> };
7
> 
8
> class MeinProjekt{
9
>   public:
10
>     MeinProjekt();
11
>   private:
12
>     Hardware meinHardware;
13
>     SPI meinSPI;
14
> };
15
> 
16
> MeinProjekt::MeinProjekt()
17
> {
18
>   meinHardware(); // first initialize Hardware
19
> 
20
>   meinSPI(SPI1, 1);
21
> }
22
>

Das ist kein C++.

von Thomas W. (goaty)


Lesenswert?

mh schrieb:
> Thomas W. schrieb:
>> Defaultkonstruktor gibt es immer. Wenn du ihn nicht hinschreibst, macht
>> es der Compiler automatisch.
>
> Das ist falsch.

"If no user-declared constructors of any kind are provided for a class 
type (struct, class, or union), the compiler will always declare a 
default constructor as an inline public member of its class."

Beitrag #6377872 wurde vom Autor gelöscht.
von mh (Gast)


Lesenswert?

Thomas W. schrieb:
> mh schrieb:
>> Thomas W. schrieb:
>>> Defaultkonstruktor gibt es immer. Wenn du ihn nicht hinschreibst, macht
>>> es der Compiler automatisch.
>>
>> Das ist falsch.
>
> "If no user-declared constructors of any kind are provided for a class
> type (struct, class, or union), the compiler will always declare a
> default constructor as an inline public member of its class."

"If no user-declared constructors of any kind are provided"
ist nicht das gleiche wie
"Defaultkonstruktor gibt es immer."

von Thomas W. (goaty)


Lesenswert?

Stimmt das war Quatsch von mir, ich denke ich hab das Problem nicht 
wirklich verstanden, also halt ich mich raus.

von Mikro 7. (mikro77)


Lesenswert?

Die "einfachste" Möglichkeit ist deine Initialisierung als erstes in 
main() durchzuführen. Wenn das ein kleines Projekt ist, dann ist diese 
einfache Lösung oft auch die beste.

Wenn du wirklich die Abhängigkeit von der "Hardware" als Klasse abbilden 
möchtest, dann müssen alle abhängigen Funktionen auch entsprechend auf 
das Hardware Objekt zugreifen. Wenn du bspw. Speicher brauchst, dann 
muss es die Funktion Hardware::malloc geben. Wenn du auf den Pwm Kanal 
#3 zugreifen willst, dann muss das über die Funktion Hardware::pwm_3 
passieren, usw. usf.

Als Konsequenz musst du einen Verweise auf das Hardware Objekt durch 
deine gesamte Implementierung durchreichen. Es ergibt sich von selbst, 
dass das Hardware Objekt am Anfang der Kette erzeugt werden muss.

In der Praxis wird statt des Durchreichens häufig ein Singleton 
genommen. D.h., dem Hardware Objekt (wovon es nur eins geben kann) ist 
eine globale Variable vorgeschaltet die beim Zugriff prüft, ob das 
Objekt bereits erzeugt wurde (...und es so automatisch beim ersten 
Zugriff erzeugt).

von Thomas W. (goaty)


Lesenswert?

Ich nochmal, so sieht es für mich eigentlich recht hübsch aus:
1
struct Hardware {
2
  Hardware() { cout << "HW init" << endl;};
3
};
4
5
class SPI {
6
  public:
7
    SPI(int i) {cout << "SPI init" << endl;};
8
};
9
10
class MeinProjekt : public Hardware {
11
  public:
12
    MeinProjekt(): meinSPI(1){  };
13
  private:
14
    SPI meinSPI;
15
};
16
MeinProjekt m;

von Vereinfacher (Gast)


Lesenswert?

Thomas W. schrieb:
> Ich nochmal, so sieht es für mich eigentlich recht hübsch aus:

Und wo wird das Hardware() aufgerufen?

Mikro 7. schrieb:
> Die "einfachste" Möglichkeit ist deine Initialisierung als erstes in
> main() durchzuführen. Wenn das ein kleines Projekt ist, dann ist diese
> einfache Lösung oft auch die beste.

Das ist aber wieder C-like.

In meiner main() steht nur folgendes und so soll das auch bleiben:
1
#include "meinProjekt.hpp"
2
3
meinProjekt meinProjekt;
4
5
int main(void){
6
  while(true){
7
    meinProjekt.run();
8
  }
9
10
  return 0;
11
}

Es ist also alles in Klassen gekapselt, ebenso die Oberklasse vom 
Projekt selber. Frei im Raum stehende Funktionen gibt es nicht.

Aber da ich keine einfache Antwort bekommen habe schätze ich, dass es 
nicht möglich ist was ich wollte.

Danke trotzdem.

von Vincent H. (vinci)


Lesenswert?

Vereinfacher schrieb:
> Das ist aber wieder C-like.

Nein, C++ ist eine Multi-Paradigmen-Sprache und kein Java.

Vereinfacher schrieb:
> Es ist also alles in Klassen gekapselt, ebenso die Oberklasse vom
> Projekt selber. Frei im Raum stehende Funktionen gibt es nicht.

Nein, es ist alles in Klassen. (Punkt)
Gekapselt ist da nichts, außer du hast Clock, Interrupts, DMA und 
Pin-Belegung auch schon bedacht.

von Thomas W. (goaty)


Lesenswert?

Vereinfacher schrieb:
> Thomas W. schrieb:
>> Ich nochmal, so sieht es für mich eigentlich recht hübsch aus:
>
> Und wo wird das Hardware() aufgerufen?
Das wird automatisch aufgerufen bei Konstruktion der Basisklasse.

https://godbolt.org/z/K8j7qT

Bin aber nicht sicher ob das überhaupt das ist was du wolltest.

: Bearbeitet durch User
von Mikro 7. (mikro77)


Lesenswert?

Vereinfacher schrieb:
> Thomas W. schrieb:
> Und wo wird das Hardware() aufgerufen?

Durch Vererbung. (in diesem Fall imho aber ein Anti-Pattern)

> Mikro 7. schrieb:
> Das ist aber wieder C-like.

Ich habe geschrieben wie du die Abhängigkeit von der Hardware 
modellieren kannst. Und das ist super-duper C++ Style. ;-)

von Sven P. (Gast)


Lesenswert?

Mikro 7. schrieb:
> Vereinfacher schrieb:
>> Thomas W. schrieb:
>> Und wo wird das Hardware() aufgerufen?
>
> Durch Vererbung. (in diesem Fall imho aber ein Anti-Pattern)

Warum? Es bildet die Abhängigkeit der Peripherie von der Hardware sauber 
ab.
Jedenfalls besser, als der krampfhafte Versuch, zwei eigentlich 
verschachtelte Aspekte nebeneinander anzuordnen.

von db8fs (Gast)


Lesenswert?

Vereinfacher schrieb:
> Und wo wird das Hardware() aufgerufen?

Na erzeugst/instanziierst du nicht ein Objekt von der Klasse 'Hardware' 
namens 'meinHardware' in einem Objekt der Klasse 'MeinProjekt' als 
erstes in deiner Konstruktorliste?

Da wird doch dein Konstruktor aufgerufen. Ist halt zustandsloses Objekt.

1
MeinProjekt::MeinProjekt() : meinHardware(), meinSPI(SPI1, 1){
2
}

> Das ist aber wieder C-like.

Was willste eigentlich? C++ programmieren mit Objektorientierung. Na 
gut, dann mach doch einfach. Reihenfolge deiner Konstruktorliste 
bestimmt die Reihenfolge der Initialisierung des Objekts, fertig ist. 
Und halt RAII machen, Konstruktoren initialisieren, Destruktoren räumen 
auf, abhängige Objekte per Referenz reinschleifen. Fertig.


> In meiner main() steht nur folgendes und so soll das auch
> bleiben:#include "meinProjekt.hpp"
>
> meinProjekt meinProjekt;
>
> int main(void){
>   while(true){
>     meinProjekt.run();
>   }
>
>   return 0;
> }

Hmm.

> Es ist also alles in Klassen gekapselt, ebenso die Oberklasse vom
> Projekt selber. Frei im Raum stehende Funktionen gibt es nicht.

Recht dogmatische Herangehensweise.

> Aber da ich keine einfache Antwort bekommen habe schätze ich, dass es
> nicht möglich ist was ich wollte.

Du hast doch was du willst. Trace doch einfach mal deine 
Funktionsaufrufe in deine Debugkonsole oder im 
Simulator/Emulator/irgendwohin. Fertsch ist.

von db8fs (Gast)


Lesenswert?

Sven P. schrieb:
>> Durch Vererbung. (in diesem Fall imho aber ein Anti-Pattern)
>
> Warum? Es bildet die Abhängigkeit der Peripherie von der Hardware sauber
> ab.
> Jedenfalls besser, als der krampfhafte Versuch, zwei eigentlich
> verschachtelte Aspekte nebeneinander anzuordnen.

Vererbung? Och Leute, ey, wirklich? Bloss weil Vererbung im realen Leben 
Spass machen kann, wieso soll das was Gutes in der Programmierung sein.

Was macht denn der Compiler bei Vererbung? Er legt implizit ne Art 
unbenannte/unsichtbare class/struct-Member von der Basisklasse in der 
Ableitung an, für Basisklassencasts:
1
class Widget
2
{
3
public:
4
 int m_x, m_y;
5
};
6
7
class Slider : public Widget
8
{
9
  int m_value;
10
public:
11
  Slider() : Widget(), m_value(0) {}
12
};

Warum ist das Moppelkotze? Zum einen Liskov, zum anderen, weil man der 
Ableitung immer die Implementierung der Basisklasse auf die Backe 
bindet. Implementieren gegen Schnittstellen mit virtueller Vererbung 
kannste jedenfalls den Hasen geben, das ist brotlose Kunst.

Und weil explizit halt immer vor implizit geht, isses halt geil, wenn 
man Komposition gegenüber Inheritance vorzieht:
1
class Widget
2
{
3
public:
4
 int m_x, m_y;
5
};
6
7
class Slider
8
{
9
  Widget m_widget;
10
  int m_value;
11
public:
12
  Slider() : m_widget(), m_value(0) {}
13
};

Haste das selbe, klemmst dir aber den ganzen Basisklassensprattel und 
förderst Layer-Bildung. Also Favor-Composition-Over-Inheritance.

von mh (Gast)


Lesenswert?

db8fs schrieb:
> Warum ist das Moppelkotze? Zum einen Liskov, zum anderen, weil man der
> Ableitung immer die Implementierung der Basisklasse auf die Backe
> bindet.
Ich kenne das Liskov substitution principle, habe aber keine Ahnung was 
du hier sagen willst. Kannst du etwas genauer erklären, was du hier mit 
Liskov meinst und was hier wem auf die Backe gebunden wird?

db8fs schrieb:
> Und weil explizit halt immer vor implizit geht, isses halt geil, wenn
> man Komposition gegenüber Inheritance vorzieht
Was ist hier expliziter als bei der Vererbung? Mal abgesehen von den 
ganzen Funktionen, die man in Slider explizit neu implementiernen muss, 
obwohl es sie in Widget schon mit gleicher Funktionalität gibt.

db8fs schrieb:
> Haste das selbe, klemmst dir aber den ganzen Basisklassensprattel und
> förderst Layer-Bildung. Also Favor-Composition-Over-Inheritance.
Was ist der "ganze Basisklassensprattel"? Wo wird hier Layer-Bildung 
gefördert und wie?

von M.K. B. (mkbit)


Lesenswert?

db8fs schrieb:
> Reihenfolge deiner Konstruktorliste bestimmt die Reihenfolge der
> Initialisierung des Objekts, fertig ist.

Das stimmt so nicht!!!

Die Reihenfolge der Konstruktion wird allein durch die Reihenfolge der 
Deklaration der Member bestimmt. Die Reihenfolge in der Initializerliste 
ist egal.

Deshalb ist es auch sinnvoll das Warning einzuschalten, das prüft ob die 
Reihenfolgen übereinstimmen. Dann ist man gezwungen beide Reihenfolgen 
konsistent zu halten.

Detail dazu hier unter "Initialization order". 
https://en.cppreference.com/w/cpp/language/constructor

von Mikro 7. (mikro77)


Lesenswert?

Sven P. schrieb:
> Mikro 7. schrieb:
>> Durch Vererbung. (in diesem Fall imho aber ein Anti-Pattern)
>
> Warum? Es bildet die Abhängigkeit der Peripherie von der Hardware sauber
> ab.
> Jedenfalls besser, als der krampfhafte Versuch, zwei eigentlich
> verschachtelte Aspekte nebeneinander anzuordnen.

Ich bin ein bisschen ambivalent bzgl. Vorgaben bei der Programmierung. 
In diesem Fall ist es für mich aber ein deutliches Anti-Pattern 
(https://en.wikipedia.org/wiki/Anti-pattern).

Grundsätzlich gilt, wenn möglich, Komposition der Vererbung vorzuziehen 
(siehe HAS-A statt IS-A: 
https://en.wikipedia.org/wiki/Composition_over_inheritance). -- Hier ist 
es nicht mal ein HAS-A (sondern eine Referenz auf die Hardware, wenn man 
die Unterscheidung im Design machen möchte).

Keine Regel ohne Ausnahme (bspw. beim Prototyping; wenn man für die 
genutzten Funktionen der "Basisklasse" keine Wrapper schreiben möchte). 
Hier sehe ich  keinen guten Grund für eine Ausnahme.

Und es besteht sowieso das "Problem", dass die Hardware nicht in einer 
Klasse gekapselt ist. Damit fehlt eh der "objektorientierte" Ansatz.

Für ein privates kleines Projekt ist die Diskussion eher belanglos: 
"Mach' was für dich selbst am besten passt". Und dem scheint der TO auch 
zu folgen. ;-)

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.