Forum: Compiler & IDEs Zustandsmaschine: Tabelle wohl nicht sinnvoll, oder mit Pointern auf Funktionen?


von J. W. (ontheway)


Angehängte Dateien:

Lesenswert?

Hi,

ich befasse mich gerade mit Zustandsautomaten, siehe Artikel 
http://www.mikrocontroller.net/articles/Statemachine

Angehangen der Code für eine ganz simple "Ampel" (wurscht was das ist), 
bei der man die Ampel anschalten kann, die Rot-Phase länger ist, man 
immer die Ampel ausschalten kann, und man diese auch blinken lassen 
kann, mit 3 Tasten gesteuert (Pollin Eval Board, so nebenbei).

Meine Transitionen hier sind, wie gesagt, bereits durch 3 Tasten 
gesteuert, im Gegensatz zu dem Link oben ist das in der Anwendung also 
schon ein bisschen ifs mit nots und ands... ich erwarte aber noch 
wesentlich mehr, denn ich möchte zum Beispiel von Zustand 3 in Zustand 4 
wechseln, wenn alle Bedingungen der Transition auf Zustand 3 weiterhin 
erfüllt sind. Beispiel: Eine Kette von Schaltern. Wenn einer auf aus 
geht, oder man den Notausschalter betätigt, dann soll zum Beispiel eine 
Abschaltungskette erfolgen. Mein Kumpel lötet mir noch eine Testplatine 
mit zum Beispiel 8 Leds und 8 Schaltern.
Ich kann ja auch beliebig verzweigen, also eine einfache Verzweigung ist 
eben die Unterscheidung zwischen Not-Aus-Schalter und weiterer 
Eskalation in spätere Zustände.

Ich sehe irgendwie nicht, wie mir eine Tabelle wie in dem Link helfen 
könnte, vielleicht checke ich das auch einfach nicht, denn bei dem Link 
oben ist ja bereits ein IF in der Tabellenabarbeitung.

Nun grübel ich gerade, wie ich meinen Code a) lesbar halte und b) nicht 
aufblähen will in eine Unmenge von Fallunterscheidungen in einem 
Zustandsautomaten.

Ich grübel über eine Tabelle, die für jeden Zustand einen Pointer auf 
eine Transitionsfunktion hat. Diese gibt dann den nächsen Zustand 
zurück.
Diese Funktionen sind natürlich auch alles andere als angenehm, da dort 
dann natürlich die ganze Logik stattfinden muss. Allerdings kann man 
dann jedem Zustand seine eindeutige Transition zuweisen...

Habt ihr einen Tipp diesbezüglich? Ein einfaches Codesample, damit ich 
das besser verstehe? Danke!

LG
Jens

von Sven B. (scummos)


Lesenswert?

Was mir dazu als Anregung einfällt (falls du C++ benutzen kannst wird 
das einfacher): Du könntest jeden Zustand durch eine Klasse darstellen, 
die eine Methode notify() hat, mit welcher du sie über eine Änderung des 
Zustands der Taster informierst. Jede dieser Klassen könnte dann aus 
notify() den neuen Zustand zurückgeben, und ihre eigene Logik in dieser 
Methode implementieren. Der aktuelle Zuständ wäre eine Instanz einer 
dieser Klassen. Dann spart man sich das riesige Switch und hat die Logik 
für die einzelnen Zustände ganz gut getrennt. Wenn man dann noch mehrere 
ähnliche Zustände in einer Klasse grupiert, vermeidet man auch, 
ähnlichen Code mehrmals zu kopieren.

Ich hab' das noch nie so umgesetzt, aber es hört sich so an, als ob es 
funktionieren könnte. ;)

Grüße,
Sven

von J. W. (ontheway)


Lesenswert?

Danke, Sven.

Habe auch schon über C++ nachgedacht, ich möchte aber vermutlich erst 
einmal C durchziehen, mal schauen... da fühle ich mich irgendwie noch 
bissi sicherer als bei C++. Wenn der Code ausartet, dann denke ich, 
nehme ich lieber Objektorientierung.

Ich probiere jetzt mal Pointer auf Funktionen in einer Matrix, mal 
schauen was das für ein Konstrukt ergibt... falls das überhaupt Sinn 
macht, ist für mich in der Praxis absolutes Neuland!

LG
Jens

von Sven B. (scummos)


Lesenswert?

Hört sich für mich an, als ob es im Endeffekt ungefähr auf dasselbe 
Konzept rausläuft wie mein Vorschlag -- bloß unübersichtlicher, weil man 
ein objektorientiertes Konzept ohne Objekte umsetzt ;)

Aber ich verstehe, dass du es erstmal mit C versuchen willst. Da bin ich 
allerdings nicht so der Experte (ich denke eher objektorientiert), 
deshalb kann ich da keine guten Tipps geben.

von J. W. (ontheway)


Lesenswert?

Hey Sven,

könntest Du denn ein Codesample aus dem Ärmel schütteln, damit ich Deine 
Konzeptidee mal ausprobieren kann? Wäre super! Kannst Dir ja mal meine 
"Ampel" anschauen. Für mich klingt das auch total logisch, das jeder 
Zustand (Instanz) intrinsisch seine eigene Logik hat, wenn ich das 
richtig verstehe. Aber ich habe noch nicht viel in C++ gemacht, da sehe 
ich im Moment überhaupt nicht, wie die Zustände übergehen. Gibt es dann 
Konstrutoren und Destruktoren? Null Peilung. Wenn Du Bock und die Zeit 
hast, kannst ja mal ein Snippet schicken...

LG
Jens

von Sven B. (scummos)


Lesenswert?

Hey,

ok, ich schreibe mal was auf, wie ich mir das vorstellen könnte, für ein 
ganz einfaches Beispiel.

Das sieht evtl. total over-engineered aus, was es auch ist für dieses 
einfache Beispiel, aber ich denke es ist relativ übersichtlich und 
leicht erweiterbar. Nur die Speicherverwaltung ist relativ mäßig (es 
verliert auch irgendwo 8 Bytes, die finde ich aber grad nicht).

Das System hat einfach nur vier Zustände, Grün, Gelb und Rot, und einen 
"Not-Aus-Zustand", der an ist, solange ein Not-Aus-Button gedrückt ist. 
Lässt man den Button wieder los, kehrt das System in den Zustand zurück, 
in dem es vorher war.

An deiner Stelle würde ich das Programm mal mit einem Debugger 
durchsteppen, dann sieht man leicht, was es tut.

ampel.h:
1
#ifndef FOOBAR_H
2
#define FOOBAR_H
3
4
#include <iostream>
5
6
// Ein Ereignis was passiert, z.B. "Button gedrückt"
7
struct Event {
8
    enum ButtonState {
9
        // das sei mal der Knopf, der zum nächsten Zustand der Ampel umschaltet
10
        NextStateButton = 1,
11
        // und das sei der Not-Aus-Knopf
12
        EmergencyShutdownButton = 2
13
    };
14
    ButtonState buttonsDown;
15
    // Konstruktor
16
    Event(const ButtonState buttonsDown) : buttonsDown(buttonsDown) { };
17
};
18
19
// Basisklasse für einen Zustand des Systems
20
class State {
21
public:
22
    // Reagiere auf ein Ereignis ("Knopf gedrückt")
23
    virtual State* handleEvent(const Event* event) = 0;
24
    // Zeige eine lesbare Interpretation des Zustands an
25
    virtual void print() = 0;
26
};
27
28
class System {
29
public:
30
    // Konstruktor
31
    System();
32
    void notify(const Event* event);
33
private:
34
    State* currentState;
35
    void changeStateTo(State* newState);
36
};
37
38
class GreenState : public State {
39
public:
40
    virtual State* handleEvent(const Event* event);
41
    virtual void print() {
42
        std::cout << "State: Green" << std::endl;
43
    }
44
};
45
46
class YellowState : public State {
47
public:
48
    virtual State* handleEvent(const Event* event);
49
    virtual void print() {
50
        std::cout << "State: Yellow" << std::endl;
51
    }
52
};
53
54
class RedState : public State {
55
public:
56
    virtual State* handleEvent(const Event* event);
57
    virtual void print() {
58
        std::cout << "State: Red" << std::endl;
59
    }
60
};
61
62
class OffState : public State {
63
public:
64
    OffState(State* beforeShutdown);
65
    virtual State* handleEvent(const Event* event);
66
    virtual void print() {
67
        std::cout << "State: Off; before shutdown: ";
68
        stateBeforeShutdown->print();
69
    }
70
private:
71
    State* stateBeforeShutdown;
72
};
73
74
#endif

Das hier ist ampel.cpp:
1
#include "ampel.h"
2
3
System::System()
4
    : currentState(new GreenState)
5
{
6
7
}
8
9
void System::notify(const Event* event)
10
{
11
    if ( event->buttonsDown & Event::EmergencyShutdownButton && ! dynamic_cast<OffState*>(currentState) ) {
12
        // Wenn der Not-Aus-Button gedrückt ist und das System nicht aus ist, gehe immer zum "Aus"-Zustand.
13
        currentState = new OffState(currentState);
14
    }
15
    else {
16
        // Nur wenn das nicht der Fall ist, lasse den aktuell aktiven Zustand entscheiden,
17
        // was als nächstes passieren soll.
18
        State* oldState = currentState;
19
        currentState = currentState->handleEvent(event);
20
        if ( oldState != currentState ) {
21
            delete oldState;
22
        }
23
    }
24
    currentState->print();
25
    delete event;
26
}
27
28
State* GreenState::handleEvent(const Event* event)
29
{
30
    if ( event->buttonsDown & Event::NextStateButton ) {
31
        // Wenn der "Nächster Zustand"-Button gedrückt ist, gehe zum nächsten Zustand.
32
        return new YellowState;
33
    }
34
    // Ansonsten, ändere den Zustand einfach gar nicht.
35
    return this;
36
}
37
38
State* YellowState::handleEvent(const Event* event)
39
{
40
    if ( event->buttonsDown & Event::NextStateButton ) {
41
        return new RedState;
42
    }
43
    return this;
44
}
45
46
State* RedState::handleEvent(const Event* event)
47
{
48
    if ( event->buttonsDown & Event::NextStateButton ) {
49
        return new GreenState;
50
    }
51
    return this;
52
}
53
54
// Konstruktor des Not-Aus-Zustands; merkt sich, in welchem Zustand das System vorher war
55
OffState::OffState(State* beforeShutdown)
56
    : stateBeforeShutdown(beforeShutdown)
57
{
58
59
}
60
61
State* OffState::handleEvent(const Event* event)
62
{
63
    if ( ! ( event->buttonsDown & Event::EmergencyShutdownButton ) ) {
64
        // Wenn der Shutdown-Button nicht gedrückt ist, kehre wieder in den
65
        // vorherigen Zustand zurück.
66
        return stateBeforeShutdown;
67
    }
68
    // Ansonten, bleibe im "Aus"-Zustand.
69
    return this;
70
}
71
72
// Das sollte eigentlich in eine Extra-Datei
73
int main() {
74
    System* myMachine = new System;
75
    // Nun ein paar Test-Ereignisse
76
    myMachine->notify(new Event(Event::NextStateButton));
77
    myMachine->notify(new Event(Event::NextStateButton));
78
    myMachine->notify(new Event(Event::EmergencyShutdownButton));
79
    myMachine->notify(new Event((Event::ButtonState) (Event::EmergencyShutdownButton | Event::NextStateButton)));
80
    myMachine->notify(new Event(Event::NextStateButton));
81
82
    delete myMachine;
83
}

Und wenn man es ausführt:
1
State: Yellow
2
State: Red
3
State: Off; before shutdown: State: Red
4
State: Off; before shutdown: State: Red
5
State: Red

Gruß,
Sven

// Edit: Ah, ja, im Destruktor von System müsste man currentState 
löschen, das sind die 8 Bytes. Ist aber wurscht.

von J. W. (ontheway)


Lesenswert?

wow, danke Sven, das werde ich mir mal genauer anschauen! Ich merke 
schon, DU KANNST das aus dem Ärmel schütteln!

Ich jammer dann bei Gelegenheit mit Fragen ;)

LG,
Jens

von Sven B. (scummos)


Lesenswert?

Sicher, wenn's Probleme gibt frag' einfach. Ganz überzeugt bin ich nicht 
davon, dass das das optimale Design ist... aber ich denke, angenehmer 
als ein riesiges switch { } ist es auf jeden Fall ;)

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> // Edit: Ah, ja, im Destruktor von System müsste man currentState
> löschen, das sind die 8 Bytes. Ist aber wurscht.

Hast du in letzter Zeit Java oder C# programmiert?
Du new-st mir da in deinem Code entschieden viel zu viel. Und das meiste 
davon sind unsinnige new.
1
int main()
2
{
3
    System* myMachine = new System;
4
5
...
6
    delete myMachine;
7
}

wozu dynamisch anlegen?
1
int main()
2
{
3
  System myMachine;
4
5
  ...
6
}
und gut ists.

Das hier
1
void System::notify(const Event* event)
2
{
3
    ....
4
    delete event;
5
}

ist ein absolutes No_no. Wozu soll die Funktion auch das Event Objekt 
löschen. Das geht diese Funktion nichts an, wo der Event herkommt! Mach 
aus dem Pointer eine Referenz ...
1
void System::notify(const Event& event)
2
{
3
    ....
4
}

der Aufrufer übergibt ein temporäres Objekt
1
int main()
2
{
3
  System myMachine;
4
5
  myMachine.notify( Event(Event::NextStateButton) );
6
  ...

und gut ists. Kein Bedarf für new und einfacher ists auch noch.

Hier:
1
State* RedState::handleEvent(const Event* event)
2
{
3
    if ( event->buttonsDown & Event::NextStateButton ) {
4
        return new GreenState;
5
    }
6
    return this;
7
}
wieder ein new ohne guten Grund.
Mach die ganze States zb als Member in System rein. Der OffState kommt 
in System rein (weil er in notify eine Sonderrolle hat), für den Rest 
leutest du von System eine AmpelSystem ab, die als Member die für eine 
Ampel möglichen Stati enthält. Jeder Status kriegt im Konstruktor mit, 
zu welchem System er gehört und kann damit dann vom AmpelSystem einen 
Pointer zu einem anderen Status anfordern. Wenn RedState einen 
GreenState anfordert, dann erzeugt es keinen neuen GreenState, sondern 
benutzt den One-and-Only GreenState, den es in AmpelSystem gibt.
1
class AmpelSystem;
2
3
class AmpelState : public State
4
{
5
  public:
6
    AmpelState( AmpelSystem* theSystem ) :
7
      system( theSystem )
8
    {}
9
10
  protected:
11
    AmpelSystem* system;
12
};
13
14
class RedState : public AmpelState
15
{
16
  ....
17
};
18
19
class GreenState : public AmpelState
20
{
21
  ....
22
};
23
24
class YellowState : public AmpelState
25
{
26
  ....
27
};
28
29
class AmpelSystem : public System
30
{
31
  public:
32
    AmpelSystem() :
33
      redState( this ),
34
      greenState( this ),
35
      yellowState( this )
36
    {
37
    }
38
39
    AmpelState* RedState() const    { return &redState; }
40
    AmpelState* GreenState() const  { return &greenState; }
41
    AmpelState* YellowState() const { return &yellowState; }
42
43
  protected:
44
    RedState    redState;
45
    GreenState  greenState;
46
    YellowState yellowState;
47
};

(Ohne das jetzt kompiliert oder vollständig lauffähig gemacht zu haben)
In deinem ganzen System brauchst du keinen einzigen new. Dadurch 
brauchst du auch keinen einzigen delete, wodurch sich hier
1
void System::notify(const Event* event)
2
{
3
    if ( event->buttonsDown & Event::EmergencyShutdownButton && ! dynamic_cast<OffState*>(currentState) ) {
4
        // Wenn der Not-Aus-Button gedrückt ist und das System nicht aus ist, gehe immer zum "Aus"-Zustand.
5
        currentState = new OffState(currentState);
6
    }
7
    else {
8
        // Nur wenn das nicht der Fall ist, lasse den aktuell aktiven Zustand entscheiden,
9
        // was als nächstes passieren soll.
10
        State* oldState = currentState;
11
        currentState = currentState->handleEvent(event);
12
        if ( oldState != currentState ) {
13
            delete oldState;
14
        }
15
    }
16
    currentState->print();
17
    delete event;
18
}

die Funktion eindampft auf
1
void System::notify(const Event& event)
2
{
3
    if ( event.buttonsDown & Event::EmergencyShutdownButton &&
4
         currentState != OffState() ) {
5
        currentState = OffState(currentState);
6
    }
7
    else {
8
        currentState = currentState->handleEvent(event);
9
    }
10
    currentState->print();
11
}

von Sven B. (scummos)


Lesenswert?

Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief 
gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe 
kein Problem dabei, dass die Kontrolle für das Event an die Funktion 
übergeben wird, die es bearbeitet (muss natürlich entsprechend 
dokumentiert sein).

Die States jedes Mal neu zu erzeugen macht natürlich, wie du sagst, bei 
diesem Beispiel auch keinen Sinn. Die Idee war aber, dass man in den 
States später noch mehr Informationen speichert, und dann wird das 
eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine 
Instanz hat. Im Endeffekt finde ich es also schon sinnvoller, für jeden 
tatsächlich neuen Zustand vom System auch ein neues Objekt zu erzeugen. 
Dazu siehe auch das Beispiel mit dem OffState, der sich halt einen 
anderen Zustand merken kann. Das wird -- wenn das System insgesamt ein 
bisschen komplizierter wird -- schwieriger, wenn es von jedem Zustand 
nur eine Instanz gibt.
Aus demselben Grund habe ich auch die ganzen news verbaut: wenn so ein 
State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern, 
dann will man das nicht jedes Mal kopieren, wenn man es zum Beispiel aus 
einer Funktion zurückgibt. Für meine 8-Byte-Objekte ist das natürlich 
völliger Unsinn, aber das sollte ja auch bloß ein Beispiel zum Erweitern 
sein.

Also zusammenfassend: ja, ich war mit der Speicherverwaltung selbst 
unzufrieden, nachdem ich es fertig geschrieben hatte, weil es dann doch 
zu viele dynamische allocs geworden sind. Ganz ohne würde ich es aber 
dann auch nicht machen.

Grüße,
Sven

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:
> Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief
> gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe
> kein Problem dabei, dass die Kontrolle für das Event an die Funktion
> übergeben wird, die es bearbeitet

Wozu?

Weder muss sich die notify Funktion um die Lebensdauer eines Events 
scheren, noch geht es die Funktion etwas an, ob ich diesen gleichen 
Event vielleicht noch in eine 2.te Zustandsmaschine stecke, die ihn 
ebenfalls auswerten will.
Sie muss es nicht wissen, also soll sie es auch nicht wissen.
Lass die Kontrolle der Dinge beeinander! Die Klasse die ein Objekt 
anlegt, ist dafür zuständig dass es zerstört wird. Natürlich gibt es 
Ausnahmen davon, aber hier liegt kein Grund dafür vor. Kein Mensch 
braucht das hier an dieser Stelle.



> Aus demselben Grund habe ich auch die ganzen news verbaut

Du machst dir (und auch dem Computer) nur das Leben unnötig schwer.

> State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern,
> dann will man das nicht jedes Mal kopieren,

Wer redet denn von kopieren?
Ich hab nirgend gesagt, dass du das Objekt kopieren sollst.
1
class AmpelSystem;
2
3
class AmpelState : public State
4
{
5
  public:
6
    AmpelState( AmpelSystem* theSystem ) :
7
      system( theSystem )
8
    {}
9
10
  protected:
11
    AmpelSystem* system;
12
};
13
14
class RedState : public AmpelState
15
{
16
  ....
17
};
18
19
class GreenState : public AmpelState
20
{
21
  ....
22
};
23
24
class YellowState : public AmpelState
25
{
26
  ....
27
};
28
29
class AmpelSystem : public System
30
{
31
  public:
32
    AmpelSystem() :
33
      redState( this ),
34
      greenState( this ),
35
      yellowState( this )
36
    {
37
    }
38
39
    AmpelState* RedState() const    { return &redState; }
40
    AmpelState* GreenState() const  { return &greenState; }
41
    AmpelState* YellowState() const { return &yellowState; }
42
43
  protected:
44
    RedState    redState;
45
    GreenState  greenState;
46
    YellowState yellowState;
47
};
48
49
50
State* RedState::handleEvent(const Event* event)
51
{
52
    if ( event->buttonsDown & Event::NextStateButton ) {
53
        return system.GreenState();
54
    }
55
    return this;
56
}

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Die States jedes Mal neu zu erzeugen macht natürlich, wie du sagst, bei
> diesem Beispiel auch keinen Sinn. Die Idee war aber, dass man in den
> States später noch mehr Informationen speichert, und dann wird das
> eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine
> Instanz hat. Im Endeffekt finde ich es also schon sinnvoller, für jeden
> tatsächlich neuen Zustand vom System auch ein neues Objekt zu erzeugen.
> Dazu siehe auch das Beispiel mit dem OffState, der sich halt einen
> anderen Zustand merken kann. Das wird -- wenn das System insgesamt ein
> bisschen komplizierter wird -- schwieriger, wenn es von jedem Zustand
> nur eine Instanz gibt.

Überhaupt nicht.
Inheritance und Polymorphie arbeiten für dich.

> Die Idee war aber, dass man in den
> States später noch mehr Informationen speichert, und dann wird das
> eventuell ein bisschen knifflig, wenn man von jedem State-Typ nur eine
> Instanz hat.

Entweder es ist der gleiche State-Typ oder er ist es nicht. Ist es 
derselbe State-Typ, dann hat er auch die gleiche Logik. Verwässere nicht 
dein Konzept!

von Sven B. (scummos)


Lesenswert?

Hi,

Karl Heinz Buchegger schrieb:
> Sven B. schrieb:
>> Ja, du hast schon Recht, die Speicherverwaltung in dem Ding ist schief
>> gelaufen. Einzig bei dem Event würde ich dir widersprechen -- ich sehe
>> kein Problem dabei, dass die Kontrolle für das Event an die Funktion
>> übergeben wird, die es bearbeitet
>
> Wozu?
>
> Weder muss sich die notify Funktion um die Lebensdauer eines Events
> scheren, noch geht es die Funktion etwas an, ob ich diesen gleichen
> Event vielleicht noch in eine 2.te Zustandsmaschine stecke, die ihn
> ebenfalls auswerten will.
Ja, wahrscheinlich hast du Recht.

>> State ein komplexes Objekt ist, mit vielleicht zwei Dutzend Membern,
>> dann will man das nicht jedes Mal kopieren,
> Wer redet denn von kopieren?
> Ich hab nirgend gesagt, dass du das Objekt kopieren sollst.
Gut, wenn du das natürlich so machst, dass es von jedem Typ nur eins 
gibt, umgehst du das natürlich. Das war aber nicht meine Absicht.

Aber irgendwie gefällt mir meine Version (mal von der Speicherverwaltung 
abgesehen ;) besser:
Bei dieser Methode muss ich für jedes Event, das ich hinzufügen will, 
einen neuen Member in State anlegen, und eine neue Member-Funktion. 
Etwas unschön, aber gut, keine große Sache.
Was mich aber viel mehr stört, ist dass das System die States so 
komplett enthält. Das Zustand vom System ist deshalb nämlich nicht durch 
ein "State"-Objekt komplett beschrieben -- sondern du musst dazu alle 
kennen. Wenn du also einen Zustand des Systems zum Beispiel speichern 
willst, reicht es nicht, ein State-Objekt zu kopieren (bei meinem 
Vorschlag könnte man sich sogar nur den Pointer merken, wenn die 
Speicherverwaltung etwas weniger ungeschickt wäre), sondern man muss die 
alle kopieren -- und zwar von Hand, weil's einzelne Attribute sind. Und 
auch daran immer rumflicken, wenn man einen neuen State hinzufügt. 
Vielleicht übersehe ich hier etwas, aber mir gefällt das nicht.

Grüße,
Sven

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Was mich aber viel mehr stört, ist dass das System die States so
> komplett enthält.

Das stört mich überhaupt nicht.
Ganz im Gegenteil. Dadurch schaffe ich einen Box, in dem klar geregelt 
ist, welche States zu welcher Maschine gehören. Niemand sagt ja, dass 
ich im Programm nur eine einzige Zustandsmaschine habe. Da der RedState 
zur Ampel gehört, kann er nur andere States die ebenfalls zur Ampel 
gehören als seine Nachfolger benennen. Er kann nicht einen State aus der 
Maschine für Bahnschranken benennen. Der gehört nicht zur Ampel und das 
AmpelSystem Objekt hält keine States, die zum BahnschrankenSystem 
gehören.

> Das Zustand vom System ist deshalb nämlich nicht durch
> ein "State"-Objekt komplett beschrieben

Du sagst hier selber

  der Zustand vom System

Da ist also vom System die Rede

> -- sondern du musst dazu alle
> kennen.

Ja, das soll vorkommen. Wenn man eine Zustandsmaschine analysieren will, 
muss man alle in dieser Maschine überhaupt möglichen Stati kennen.

> Wenn du also einen Zustand des Systems zum Beispiel speichern
> willst, reicht es nicht, ein State-Objekt zu kopieren (bei meinem
> Vorschlag könnte man sich sogar nur den Pointer merken, wenn die
> Speicherverwaltung etwas weniger ungeschickt wäre), sondern man muss die
> alle kopieren

Warum?
Das System Objekt, weiß ja, welches der jetzt gerade aktive Zustand ist.

Ausserdem sagst du ja jetzt gerade selber:
Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen 
Zustand! Das System, das ist aber die Gesamtheit aller Zustände. Woher 
weißt du denn, ob sich nicht ein anderer Zustand ebenfalls speihern 
will, weil er sich irgendwas wichtiges merken muss? Das kann nur der 
Zustand wissen. Das System kennt alle Zustände die zu ihm gehören und 
gibt jedem Gelegenheit, sich aufs File zu speichern.

> -- und zwar von Hand, weil's einzelne Attribute sind.

Na ja.
Die Phantasie hätt ich dir jetzt aber schon zugetraut, dass mans sich da 
auch einen Vektor machen kann, denn man zum Speichern durchiteriert oder 
was anderes erfindet.

> Und
> auch daran immer rumflicken, wenn man einen neuen State hinzufügt.

Richtig.
Und das ist auch gut so!
Den der neue Zustand gehört zum Ampelsystem. Er gehört nicht zum 
AndreaskreuzSystem oder zum BahnschrankenSystem.

So wie du es jetzt machst - da kannst du dir OOP auch sparen. Mach ein 
paar Strukturen, mach ein paar Funktionen und gut ists. Hat auch den 
Vorteil, dass du weniger Tipparbeit hast.
Aber wenn du OOP arbeiten willst, dann musst du das auch ernst nehmen. 
Welche Objekte gibt es und noch viel wichtiger: Wie hängen sie zusammen 
und wie kann ich diese Zusammenhänge im Code ausdrücken, so dass mir der 
Compiler überwacht, dass diese Zusammenhänge gewährleistet sind.

von Karl H. (kbuchegg)


Lesenswert?

> Welche Objekte gibt es und noch viel wichtiger: Wie hängen sie zusammen
> und wie kann ich diese Zusammenhänge im Code ausdrücken, so dass mir der
> Compiler überwacht, dass diese Zusammenhänge gewährleistet sind.

Die OOP Idee ermöglicht es dir 2 große, wichtige Zusammenhänge in 
Klassenform auszudrücken

*  IST EIN

   Im Sinne von "Ein PKW ist ein Kraftfahrzeug"

    class Kraftfahrzeug
    {
    };

    class PKW : public Kraftfahrzeug
    {
    };

*  HAT EIN   (oder: besteht aus)

     Im Sinn von "Ein Kraftfahrzeug hat einen Motor"

     class Kraftfahrzeug
     {
       protected:
         Motor   motor_;
     };

     Wenn an dieser Stelle wieder verschiedene Motortypen möglich
     sind, dann muss man leider aus technischen Gründen einen Pointer
     benutzen. Das ändert aber nichts daran, dass eine HAT-EIN Beziehung
     auf einen Klassenmember rausläuft.


Eine Zustandsmaschine besteht aus Zuständen (no na), also ....

von Sven B. (scummos)


Lesenswert?

Hi,

> Das stört mich überhaupt nicht.
> Ganz im Gegenteil. Dadurch schaffe ich einen Box, in dem klar geregelt
> ist, welche States zu welcher Maschine gehören.
Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState 
: public BaseState, und alle States von MySystem müssen davon erben. 
Dann ist auch klar, welche States zu welcher Maschine gehören.

> Niemand sagt ja, dass
> ich im Programm nur eine einzige Zustandsmaschine habe. Da der RedState
> zur Ampel gehört, kann er nur andere States die ebenfalls zur Ampel
> gehören als seine Nachfolger benennen. Er kann nicht einen State aus der
> Maschine für Bahnschranken benennen. Der gehört nicht zur Ampel und das
> AmpelSystem Objekt hält keine States, die zum BahnschrankenSystem
> gehören.
Wie gesagt, das lässt sich durch Vererbung sehr einfach erzwingen.

>> Das Zustand vom System ist deshalb nämlich nicht durch
>> ein "State"-Objekt komplett beschrieben
> Du sagst hier selber
>   der Zustand vom System
> Da ist also vom System die Rede
Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein, 
in dem sich das System befindet. Solch ein State soll das System 
komplett beschreiben. Das war meine Idee. Natürlich kann man auch andere 
Ideen haben, die funktionieren, aber so hatte ich mir das überlegt.

> Ja, das soll vorkommen. Wenn man eine Zustandsmaschine analysieren will,
> muss man alle in dieser Maschine überhaupt möglichen Stati kennen.
Ich bin mir immer noch nicht sicher, welche Relevanz die Attribute von 
StateA haben sollen, wenn das System sich zur Zeit in StateB befindet. 
Was haben die für eine Bedeutung?

> Warum?
> Das System Objekt, weiß ja, welches der jetzt gerade aktive Zustand ist.
Okay.

> Ausserdem sagst du ja jetzt gerade selber:
> Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen
> Zustand!
Naja, das widerspricht jetzt deinem vorherigen Argument. Ich kann nur 
mit einem davon argumentieren, also nehme ich mal dieses ;)

> Das System, das ist aber die Gesamtheit aller Zustände. Woher
> weißt du denn, ob sich nicht ein anderer Zustand ebenfalls speihern
> will, weil er sich irgendwas wichtiges merken muss? Das kann nur der
> Zustand wissen. Das System kennt alle Zustände die zu ihm gehören und
> gibt jedem Gelegenheit, sich aufs File zu speichern.
Wie gesagt, da verfolgst du einfach einen anderen Ansatz als ich. Mein 
Ansatz ist: Das System ist durch eine Instanz von "State" komplett 
beschrieben.

>> -- und zwar von Hand, weil's einzelne Attribute sind.
> Na ja.
> Die Phantasie hätt ich dir jetzt aber schon zugetraut, dass mans sich da
> auch einen Vektor machen kann, denn man zum Speichern durchiteriert oder
> was anderes erfindet.
Ja, aber richtig elegant wird das trotzdem nicht, weil man muss ja dann 
den GreenState in dem Vektor identifizieren können... braucht man also 
nochmal einen hässlichen enum oder sonstwas...

>> Und
>> auch daran immer rumflicken, wenn man einen neuen State hinzufügt.
> Richtig.
> Und das ist auch gut so!
> Den der neue Zustand gehört zum Ampelsystem. Er gehört nicht zum
> AndreaskreuzSystem oder zum BahnschrankenSystem.
Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im 
Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?

Grüße,
Sven

von Sven B. (scummos)


Lesenswert?

> <OOP-Lehrgang>
Ja -- mein System hat einen aktuellen Zustand. Einen. Nicht fünf.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState
> : public BaseState, und alle States von MySystem müssen davon erben.
> Dann ist auch klar, welche States zu welcher Maschine gehören.

Zeig mal, wie du das mit der Methode der Basisklasse unterbringst :-)

Die Methode
1
void System::notify(const Event& event)
2
{
3
    if ( event.buttonsDown & Event::EmergencyShutdownButton &&
4
         currentState != OffState() ) {
5
        currentState = OffState(currentState);
6
    }
7
    else {
8
        currentState = currentState->handleEvent(event);
9
    }
10
    currentState->print();
11
}
bleibt unangetastet und wird auch nicht von einer abgeleiteten Klasse 
überschrieben.

handleEvent liefert einen Pointer auf ein von State abgeleitete Klasse. 
Irgendeine Klasse. Solange sie nur von State abgeleitet ist, passt das. 
D.h. jede deiner State Klassen kann irgendeinen State Pointer liefern. 
Selbst dann, wenn der gar nicht zu dieser Maschine gehört.


> Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein,
> in dem sich das System befindet. Solch ein State soll das System
> komplett beschreiben.

Moment.
Ein State ist also das komplette System?

Sorry - Sven
Ich versteh ja, dass du deine Systematik verteidigen willst, aber du 
beginnst dich jetzt in Widersprüche zu verwickeln.
Entweder du hast ein System oder du hast States. Aber ein State kann 
nicht das System sein, sonst würdest du eines von beiden nicht 
benötigen.

> Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im
> Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?

Weil dich der COmpiler dazu zwingt, dass du nicht einfach irgendwas 
machen kannst, sondern du einen neuen Zustand ins System regelrecht 
einbauen musst. Und sowas verhindert Fehler. Alles was darauf basiert, 
dass du dem Programmierer vertrauen musst, ist ein Garant dafür, dass er 
irgendwann Fehler macht. Alles was du so gestaltest, dass der Compiler 
es überprüfen kann, ist ein Garant dafür, dass derartige Fehler auch vom 
Compiler gefunden werden.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:
>> <OOP-Lehrgang>
> Ja -- mein System hat einen aktuellen Zustand. Einen. Nicht fünf.

Ich hab auch nur einen aktuellen Zustand.
Aber das Gesamtsystem kann 5 verschiedene Zustände annehmen. Einer davon 
ist der aktuelle.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Ich bin mir immer noch nicht sicher, welche Relevanz die Attribute von
> StateA haben sollen, wenn das System sich zur Zeit in StateB befindet.
> Was haben die für eine Bedeutung?

Das kommt jetzt aber sehr auf das System an.
In diesem Fall haben die States ja überhaupt keine Attribute.
Also was macht dir dann sorgen?


>> Ausserdem sagst du ja jetzt gerade selber:
>> Du willst das System speichern. DAS SYSTEM, nicht einen einzelnen
>> Zustand!
> Naja, das widerspricht jetzt deinem vorherigen Argument.

Inwiefern soll das ein Widerspruch sein?
Ich sehe da ehrlich gesagt keinen Widerspruch.


> Ansatz ist: Das System ist durch eine Instanz von "State" *komplett*
> beschrieben.

Wozu brauche ich dann 'das System' überhaupt noch?
Aus welchem Grunde hast du es eingeführt? Alles was in der System-Klasse 
ist, könnte dann doch genausogut in der State-Klasse sein, wenn ein 
State sowieso die komplette Maschine beschreibt?

von Sven B. (scummos)


Lesenswert?

Hi,

Karl Heinz Buchegger schrieb:
> Sven B. schrieb:
>
>> Naja, das wäre mit Vererbung aber eleganter zu regeln. MySystemBaseState
>> : public BaseState, und alle States von MySystem müssen davon erben.
>> Dann ist auch klar, welche States zu welcher Maschine gehören.
>
> Zeig mal, wie du das mit der Methode der Basisklasse unterbringst :-)
>
> Die Methode
>
1
> void System::notify(const Event& event)
2
> {
3
>     if ( event.buttonsDown & Event::EmergencyShutdownButton &&
4
>          currentState != OffState() ) {
5
>         currentState = OffState(currentState);
6
>     }
7
>     else {
8
>         currentState = currentState->handleEvent(event);
9
>     }
10
>     currentState->print();
11
> }
12
>
> bleibt unangetastet und wird auch nicht von einer abgeleiteten Klasse
> überschrieben.
Naja, handleEvent() muss dann natürlich einen MySystemState* 
zurückgeben. Aber das ist ja kein Problem.

> handleEvent liefert einen Pointer auf ein von State abgeleitete Klasse.
> Irgendeine Klasse. Solange sie nur von State abgeleitet ist, passt das.
> D.h. jede deiner State Klassen kann irgendeinen State Pointer liefern.
> Selbst dann, wenn der gar nicht zu dieser Maschine gehört.
Jaja, so wie das da steht natürlich schon. Aber ich wollte in diesem 
Beispiel nicht anfangen eine Klasse State, und eine Klasse 
MyMachineState : public State { } einzuführen, weil's einfach 
überflüssig gewesen wäre. Aber es ist uns doch beiden klar, wie man es 
machen würde.

>> Ja, aber wie das Wort "State" sagt, soll so ein State ein Zustand sein,
>> in dem sich das System befindet. Solch ein State soll das System
>> komplett beschreiben.
> Moment.
> Ein State ist also das komplette System?
> Sorry - Sven
> Ich versteh ja, dass du deine Systematik verteidigen willst, aber du
> beginnst dich jetzt in Widersprüche zu verwickeln.
Nein, das ist genau das was ich meine: Ein State ist das komplette 
System. Ist ja in meiner Implementierung auch so, die System-Klasse ist 
nur ein Container für eine (!) Instanz von State mit ein paar 
Funktionen, die diese manipulieren. Zusätzlich könnte die System-Instanz 
noch ein paar statische Eigenschaften haben, die das System an sich 
charakterisieren, sich aber nach dem Erstellen des Systems nicht mehr 
ändern. Ich weiß schon, dass du was anderes willst ;)

> Entweder du hast ein System oder du hast States. Aber ein State kann
> nicht das System sein, sonst würdest du eines von beiden nicht
> benötigen.
Streng genommen könnte man in meinem Konzept die System-Klasse 
weglassen, ja. Sieh es als Wrapper um eine Instanz von State, die immer 
ausgetauscht wird.

>> Hm, ja, schon, aber inwiefern macht das jetzt eine zusätzliche Stelle im
>> Code, die ich bei jeder Änderung anpassen muss, zu einem Vorteil?
> Weil dich der COmpiler dazu zwingt, dass du nicht einfach irgendwas
> machen kannst, sondern du einen neuen Zustand ins System regelrecht
> einbauen musst. Und sowas verhindert Fehler. Alles was darauf basiert,
> dass du dem Programmierer vertrauen musst, ist ein Garant dafür, dass er
> irgendwann Fehler macht. Alles was du so gestaltest, dass der Compiler
> es überprüfen kann, ist ein Garant dafür, dass derartige Fehler auch vom
> Compiler gefunden werden.
Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier 
in eine Datei schreibe, interessiert das den Compiler ja nicht...

Grüße,
Sven

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Naja, handleEvent() muss dann natürlich einen MySystemState*
> zurückgeben.

Niemand zwingt dich dazu.
Für
        currentState = currentState->handleEvent(event);

ist ein Returnwert als MySysteState* genausogut wie ein BaseState* oder 
ein AmpelState* oder ein XYZState*

Solange die alle irgendwie von State abgeleitet sind, ist das für diese 
Zuweisung absolut perfekt.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:


> Nein, das ist genau das was ich meine: Ein State ist das komplette
> System. Ist ja in meiner Implementierung auch so, die System-Klasse ist
> nur ein Container für eine (!) Instanz von State mit ein paar
> Funktionen, die diese manipulieren.

(Für einen Pointer, aber ist egal)

class State
{

   ....

   static State* currentState;
};


jetzt ist der currentState im State selber.
Ist also überhaupt kein Problem.

> Zusätzlich könnte die System-Instanz
> noch ein paar statische Eigenschaften haben, die das System an sich
> charakterisieren

Was jetzt.
Beschreibt jetzt ein State das komplette System oder nicht?
Edit: gerade gesehen, du sagst dann später ja selber, dass du das System 
an sich gar nicht benötigst.

> Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier
> in eine Datei schreibe, interessiert das den Compiler ja nicht...

Das ist richtig.
Aber da kommt dann ins Spiel, auf welche Art und Weise ich genau die in 
einem System möglichen States beschreibe.
Wenn ich die als Member einzeln aufführe, dann muss ich drauf achten - 
ja. Genauso wie ich in einem Copy Constructor (wenn ich ihn selber 
schreibe) darauf achten muss, alle Member zu behandeln.
Erfinde ich mir einen Container in dem alle States drinnen sind, dann 
brauch ich das nicht, hab aber den Mehraufwand, dass ich eine 
Identifizierungsmöglichkeit (ein enum oder sonst was) benötige.

von Sven B. (scummos)


Lesenswert?

Mensch, ich meine natürlich, dass ich in MyMachineState... okay, ich 
schreib's auf ;)
1
class State {
2
// Irgendein Zustand irgendeines Systems
3
};
4
5
class MyMachineState : public State {
6
  virtual MyMachineState* handleEvent();
7
};
8
9
class YourMachineState : public State {
10
  virtual YourMachineState* handleEvent();
11
};
12
13
class MyMachine {
14
  MyMachineState* currentState;
15
}

Grüße,
Sven

von Karl H. (kbuchegg)


Lesenswert?

>  Sieh es als Wrapper um eine Instanz von State, die immer
ausgetauscht wird.

Und die immer neu erzeugt wird!
Das war ja der Dreh und Angelpunkt der ganzen Sache.
Objekterzeugung ist vergleichsweise teuer!

von Sven B. (scummos)


Lesenswert?

Karl Heinz Buchegger schrieb:
> (Für einen Pointer, aber ist egal)
>
> class State
> {
>
>    ....
>
>    static State* currentState;
> };
>
>
> jetzt ist der currentState im State selber.
> Ist also überhaupt kein Problem.
Tut mir Leid, ich verstehe nicht, was du hier sagen willst. Kannst du 
das nochmal anders formulieren?

>> Zusätzlich könnte die System-Instanz
>> noch ein paar statische Eigenschaften haben, die das System an sich
>> charakterisieren
>
> Was jetzt.
> Beschreibt jetzt ein State das komplette System oder nicht?
> Edit: gerade gesehen, du sagst dann später ja selber, dass du das System
> an sich gar nicht benötigst.
Ja, abgesehen von eventuellen festen Werten, die das System noch hat. 
Aber wenn man die nicht mag, kann man die auch weglassen.

>> Naja, das stimmt ja aber nicht. Wenn ich von den fünf Membern nur vier
>> in eine Datei schreibe, interessiert das den Compiler ja nicht...
>
> Das ist richtig.
> Aber da kommt dann ins Spiel, auf welche Art und Weise ich genau die in
> einem System möglichen States beschreibe.
> Wenn ich die als Member einzeln aufführe, dann muss ich drauf achten -
> ja. Genauso wie ich in einem Copy Constructor (wenn ich ihn selber
> schreibe) darauf achten muss, alle Member zu behandeln.
> Erfinde ich mir einen Container in dem alle States drinnen sind, dann
> brauch ich das nicht, hab aber den Mehraufwand, dass ich eine
> Identifizierungsmöglichkeit (ein enum oder sonst was) benötige.
Sicher, das Problem ist lösbar. Aber ein Vorteil dieser Methode ist es 
nicht gerade.
Vor allem ist mir immer noch nicht klar, was du dir davon versprichst, 
mehrere Instanzen von State für ein System zu haben. Was bringt dir das 
an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz 
von State"-Variante?

Grüße,
Sven

von Sven B. (scummos)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Und die immer neu erzeugt wird!
> Das war ja der Dreh und Angelpunkt der ganzen Sache.
> Objekterzeugung ist vergleichsweise teuer!
Naja. Von was reden wir hier denn. Ich dachte, es geht um ein paar 
Buttons, die gedrückt werden, und eine 2GHz-CPU, die die States 
wechselt. Da brauchen wir über das alloc von den 16 Bytes ja wohl nicht 
zu streiten.
Wenn das auf einem 8-Bit uC laufen soll, sieht die Sache natürlich 
anders aus, und dann gebe ich auch sofort zu, dass meine Lösung 
ungeeignet ist.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:


> Vor allem ist mir immer noch nicht klar, was du dir davon versprichst,
> mehrere Instanzen von State für ein System zu haben. Was bringt dir das
> an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz
> von State"-Variante?

Das mir der Compiler bei

State* RedState::handleEvent(const Event* event)
{
    if ( event->buttonsDown & Event::NextStateButton ) {
        return new SchrankenZuState;
    }
    return this;
}


auf die Finger klopft, weil "Schranke Zu" bei einer Ampel nichts 
verloren hat! Der gehört nicht zur Ampel, aber nichts und niemand 
hindert mich daran das zu tun!

von Karl H. (kbuchegg)


Lesenswert?

1
> class State {
2
> // Irgendein Zustand irgendeines Systems
3
> };
4
> 
5
> class MyMachineState : public State {
6
>   virtual MyMachineState* handleEvent();
7
> };
8
> 
9
> class YourMachineState : public State {
10
>   virtual YourMachineState* handleEvent();
11
> };

das rettet dich aber immer noch nicht.
1
void System::notify(const Event& event)
2
{
3
  ...
4
  currentState = currentState->handleEvent();
5
}

geht trotzdem immer noch mit einem bunten Mix von MyMachineState und 
YourMachineState. Die beiden ReturnTypen sind kovariant!

1
class MyMachine {
2
  MyMachineState* currentState;
3
}
Ok. Und wozu brauchst du dann noch die gemeinsame Basisklasse State?

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:
> Karl Heinz Buchegger schrieb:
>> Und die immer neu erzeugt wird!
>> Das war ja der Dreh und Angelpunkt der ganzen Sache.
>> Objekterzeugung ist vergleichsweise teuer!
> Naja. Von was reden wir hier denn. Ich dachte, es geht um ein paar
> Buttons, die gedrückt werden, und eine 2GHz-CPU, die die States
> wechselt. Da brauchen wir über das alloc von den 16 Bytes ja wohl nicht
> zu streiten.

Ok. Ich geb mich geschlagen.

von Sven B. (scummos)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Sven B. schrieb:
>> Vor allem ist mir immer noch nicht klar, was du dir davon versprichst,
>> mehrere Instanzen von State für ein System zu haben. Was bringt dir das
>> an Gewinn gegenüber meiner "Ein System ist eigentlich nur eine Instanz
>> von State"-Variante?
>
> Das mir der Compiler bei
>
> State* RedState::handleEvent(const Event* event)
> {
>     if ( event->buttonsDown & Event::NextStateButton ) {
>         return new SchrankenZuState;
>     }
>     return this;
> }
>
>
> auf die Finger klopft, weil "Schranke Zu" bei einer Ampel nichts
> verloren hat! Der gehört nicht zur Ampel, aber nichts und niemand
> hindert mich daran das zu tun!
Das soll der komplette Vorteil sein? Und dafür speicherst du fünfmal 
so viele Objekte wie ich und beschwerst dich dann über die Performance 
meiner Lösung? Das finde ich etwas merkwürdig.
Es hindert dich in keinem Fall jemand daran, einfach 
reinterpret_cast<State*>(0xDEADBEEF) zurückzugeben. Ein assert in 
"System" würde solcherart Fehler also sogar zuverlässiger vermeiden.

Aber gut, dann mache ich das halt anders, wenn diese eine Zeile pro 
State Machine zuviel Schreibaufwand ist:
1
template <typename System> class State 
2
public:
3
  virtual State<System>* handleEvent();
4
};
5
6
class MySystem { 
7
public:
8
  State<MySystem>* state;
9
};
10
class YourSystem {
11
  State<YourSystem>* state;
12
};
13
14
class MySystemState1 : public State<MySystem> { };
15
class YourSystemState1 : public State<YourSystem> { };
16
17
MySystem x;
18
x.state = new YourSystemState1(); // Fehler

Bitteschön.

Grüße,
Sven

von Sven B. (scummos)


Lesenswert?

> Die beiden ReturnTypen sind kovariant!
Ein weiteres Problem, welches nur auf dem Papier existiert. Bau' halt 
einen virtuellen Destruktor in State.

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Das soll der komplette Vorteil sein?

Schön langsam frag ich mich wozu du überhaupt Klassen hast.
Ein Pointer, 5 Funktionen und gut ists.

Wer braucht denn schon Sicherheit oder Organisation oder 
wiederverwendbare Module in seinen Programmen?
1
typedef void* (*stateFnct)( Event* event );
2
3
stateFnct currentState;
4
5
stateFnct redState( Event* event )
6
{
7
  ..
8
  return grnState;
9
}
10
11
statFnct grnState( Event* event )
12
{
13
  ...
14
  return yellowState;
15
}
16
17
stateFnct yellowState( Event* event )
18
{
19
  ...
20
  return redState;
21
}
22
23
int main()
24
{
25
  Event ev;
26
27
  currentState = redState;
28
29
  currentState = (StateFnct)currentState( &ev )
30
  currentState = (StateFnct)currentState( &ev )
31
  currentState = (StateFnct)currentState( &ev )
32
}

und gut ists.
Alles andere ist doch nur Tipparbeit für nichts.

von Sven B. (scummos)


Lesenswert?

Dieser Beitrag war jetzt etwas unsachlich, weil er in keinerlei 
Zusammenhang zu den vorherigen Beiträgen steht ;)

Das Beispiel was ich gepostet habe zeigt doch, dass man dasselbe type 
checking auch mit meinem Aufbau bekommen kann. Wo ist also das Problem?

Gruß,
Sven

von Karl H. (kbuchegg)


Lesenswert?

Sven B. schrieb:

> Das Beispiel was ich gepostet habe zeigt doch, dass man dasselbe type
> checking auch mit meinem Aufbau bekommen kann. Wo ist also das Problem?

Was hab ich davon, wenn ich bei jeder neuen Zustandsmaschine erst recht 
wieder das Rad von vorne aufrollen und mehr oder weniger bei 0 anfangen 
muss?

Da kann ich mir das ganze Geplänkel auch gleich sparen und spar mir 40% 
Tipparbeit.

von foo (Gast)


Lesenswert?

Um komplizierte Statemachines in C zu implementieren bieten sich 
Protothreads an.

von Falk B. (falk)


Lesenswert?

Mann O Mann. Wie man Probleme mit HighTec löst, die man ohne sie nie 
hätte.
Kein Wunder dass heute jede Eieruhr einen 200 MHZ ARM9 braucht . . .

von Ingo (Gast)


Lesenswert?

Man beachte die Uhrzeiten! Schlaft ihr garnicht mal n bissel?

von Peter D. (peda)


Lesenswert?

J. W. schrieb:
>    if (NOT TASTE2 AND NOT TASTE3) {
>      Ampel1( ROT );
>      state = S_WARTE_GELB;
>    }
>    else if (TASTE3)
>    {
>      state = S_BLINK_EINS;
>    }
>    else state = S_AUS;

Ein wirklich sehr schönes Beispiel, wie man Sachen unnötig kompliziert 
machen kann, wenn man sich standhaft weigert, eine vernünftige 
Entprellroutine zu verwenden.
Bravo!

von Sven B. (scummos)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Was hab ich davon, wenn ich bei jeder neuen Zustandsmaschine erst recht
> wieder das Rad von vorne aufrollen und mehr oder weniger bei 0 anfangen
> muss?
Ist bei Beitrag "Re: Zustandsmaschine: Tabelle wohl nicht sinnvoll, oder mit Pointern auf Funktionen?" nicht der 
Fall.

Falk Brunner schrieb:
> Mann O Mann. Wie man Probleme mit HighTec löst, die man ohne sie nie
> hätte.
> Kein Wunder dass heute jede Eieruhr einen 200 MHZ ARM9 braucht . . .
Für eine Eieruhr ist das zugegebenermaßen Quatsch.
Davon abgesehen, wenn man das geschickt aufschreibt, wird der GCC daraus 
Code erzeugen, der nicht wesentlich komplizierter ist als der mit dem 
Switch.

Ingo schrieb:
> Man beachte die Uhrzeiten! Schlaft ihr garnicht mal n bissel?
Ja, es ist beunruhigend ;)

Grüße,
Sven

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.