C Stan-konstrukcja maszyny

Tworzę mały projekt w mieszanym C i C++. Buduję jedną małą maszynę stanową w sercu jednej z moich nici roboczych.

Zastanawiałem się, czy nie podzieliłbyś się swoimi technikami projektowania maszyn państwowych.

Uwaga: jestem przede wszystkim po wypróbowanych i przetestowanych technikach implementacji.

Aktualizacja: bazując na wszystkich wspaniałych wkładach zebranych na SO, zdecydowałem się na tę architekturę:

Tutaj wpisz opis obrazka

Author: jldupont, 2009-10-30

24 answers

Maszyny stanowe, które projektowałem wcześniej (C, nie c++), sprowadzały się do tablicy struct i pętli. Struktura składa się zasadniczo ze stanu i zdarzenia (do wyszukiwania) oraz funkcji, która zwraca nowy stan, coś w stylu:

typedef struct {
    int st;
    int ev;
    int (*fn)(void);
} tTransition;

Następnie definiujesz swoje stany i zdarzenia za pomocą prostych definicji (ANY są specjalnymi znacznikami, patrz poniżej):

#define ST_ANY              -1
#define ST_INIT              0
#define ST_ERROR             1
#define ST_TERM              2
: :
#define EV_ANY              -1
#define EV_KEYPRESS       5000
#define EV_MOUSEMOVE      5001

Następnie definiujesz wszystkie funkcje, które są wywoływane przez przejścia:

static int GotKey (void) { ... };
static int FsmError (void) { ... };

Wszystkie te funkcje są zapisany tak, aby nie pobierał żadnych zmiennych i zwracał nowy stan dla maszyny stanowej. W tym przykładzie zmienne globalne są używane do przekazywania dowolnych informacji do funkcji stanu w razie potrzeby.

Używanie globali nie jest takie złe, jak się wydaje, ponieważ FSM jest zwykle zamknięty w jednej jednostce kompilacji, a wszystkie zmienne są statyczne dla tej jednostki (dlatego użyłem cudzysłowów wokół "global" powyżej - są one bardziej współdzielone w FSM, niż naprawdę globalne). Jak w przypadku wszystkich globali, wymaga care.

Tablica transitions definiuje następnie wszystkie możliwe przejścia i funkcje, które zostaną wywołane dla tych przejść (łącznie z ostatnią funkcją catch-all):

tTransition trans[] = {
    { ST_INIT, EV_KEYPRESS, &GotKey},
    : :
    { ST_ANY, EV_ANY, &FsmError}
};
#define TRANS_COUNT (sizeof(trans)/sizeof(*trans))

Oznacza to, że jeśli jesteś w stanie ST_INIT i otrzymujesz Zdarzenie EV_KEYPRESS, wykonaj połączenie do GotKey.

Działanie FSM staje się wtedy relatywnie prostą pętlą:

state = ST_INIT;
while (state != ST_TERM) {
    event = GetNextEvent();
    for (i = 0; i < TRANS_COUNT; i++) {
        if ((state == trans[i].st) || (ST_ANY == trans[i].st)) {
            if ((event == trans[i].ev) || (EV_ANY == trans[i].ev)) {
                state = (trans[i].fn)();
                break;
            }
        }
    }
}

Jak wspomniano powyżej, zwróć uwagę na użycie ST_ANY jako dzikich kart, pozwalających na wywołanie funkcji bez względu na to, obecny stan. EV_ANY działa podobnie, pozwalając dowolnemu zdarzeniu w określonym stanie wywołać funkcję.

Może również zagwarantować, że jeśli dotrzesz do końca tablicy przejść, pojawi się błąd stwierdzający, że Twój FSM nie został poprawnie zbudowany (za pomocą kombinacji ST_ANY/EV_ANY.

Użyłem kodu podobnego do tego w wielu projektach komunikacyjnych, takich jak wczesne wdrożenie stosów komunikacyjnych i protokołów dla systemów wbudowanych. Dużą zaletą była jego prostota i względna łatwość zmiany tablicy przejść.

Nie mam wątpliwości, że będą abstrakcje wyższego poziomu, które mogą być bardziej odpowiednie w dzisiejszych czasach, ale podejrzewam, że wszystkie sprowadzą się do tego samego rodzaju struktury.


I, jak stwierdza ldog w komentarzu, możesz całkowicie uniknąć globali przekazując wskaźnik struktury do wszystkich funkcji (i używając go w pętli zdarzeń). Pozwoli to wielu maszynom stanowym działać obok siebie bez zakłócenia.

Po prostu utwórz typ struktury, który przechowuje dane specyficzne dla maszyny (stan na minimum) i użyj tego zamiast globali.

Powodem, dla którego rzadko to robię, jest to, że większość maszyn stanowych, które napisałem, były typami singletonów (na przykład jednorazowe, na początku procesu, odczyt plików konfiguracyjnych), nie wymagające uruchamiania więcej niż jednej instancji. Ale ma wartość, jeśli musisz uruchomić więcej niż jeden.

 164
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-01 14:05:32

Inne odpowiedzi są dobre, ale bardzo" lekka " implementacja, której użyłem, gdy maszyna stanowa jest bardzo prosta wygląda następująco:

enum state { ST_NEW, ST_OPEN, ST_SHIFT, ST_END };

enum state current_state = ST_NEW;

while (current_state != ST_END)
{
    input = get_input();

    switch (current_state)
    {
        case ST_NEW:
        /* Do something with input and set current_state */
        break;

        case ST_OPEN:
        /* Do something different and set current_state */
        break;

        /* ... etc ... */
    }
}

Użyłbym tego, gdy maszyna stanowa jest na tyle prosta, że podejście wskaźnika funkcji i tabeli przejścia stanu jest przesadą. Jest to często przydatne do parsowania znak po znaku lub słowo po słowie.

 76
Author: caf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 05:04:36

Przepraszam za złamanie każdej reguły w informatyce, ale maszyna stanowa jest jednym z niewielu (mogę liczyć tylko dwa) miejsc, w których deklaracja goto jest nie tylko bardziej skuteczna, ale także sprawia, że Twój kod jest czystszy i łatwiejszy do odczytania. Ponieważ goto instrukcje są oparte na etykietach, możesz nazwać swoje stany zamiast śledzić bałagan liczb lub używać enum. To również sprawia, że znacznie czystszy Kod, ponieważ nie potrzebujesz wszystkich dodatkowych wskaźników funkcji lub ogromnego przełącznika polecenia i pętle while. Czy wspomniałem, że jest bardziej wydajny?

Oto jak może wyglądać maszyna Państwowa:

void state_machine() {
first_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }

second_state:
    // Do some stuff here
    switch(some_var) {
    case 0:
        goto first_state;
    case 1:
        goto second_state;
    default:
        return;
    }
}
Rozumiesz ogólny pomysł. Chodzi o to, że można zaimplementować maszynę stanową w skuteczny sposób i taki, który jest stosunkowo łatwy do odczytania i krzyczy na czytelnika, że patrzy na maszynę stanową. Zauważ, że jeśli używasz goto wypowiedzi, musisz zachować ostrożność, ponieważ bardzo łatwo jest strzelić sobie w stopę.
 33
Author: Jason E,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-31 03:33:31

Możesz rozważyć kompilator maszyn stanowych http://smc.sourceforge.net/

To wspaniałe narzędzie open source akceptuje Opis Maszyny stanowej w prostym języku i kompiluje go do dowolnego z kilkunastu języków-w tym C i C++. Samo narzędzie jest napisane w języku Java i może być dołączone jako część kompilacji.

Powodem, aby to zrobić, zamiast ręcznego kodowania za pomocą wzorca stanu GoF lub jakiegokolwiek innego podejścia, jest to, że gdy maszyna stanowa jest wyrażona jako kod, podstawowa struktura ma tendencję do znikania pod ciężarem kotła, który musi być wygenerowany, aby go wspierać. Zastosowanie tego podejścia daje doskonałe rozdzielenie obaw i utrzymuje strukturę swojej maszyny Państwowej "widoczną". Automatycznie wygenerowany kod przechodzi do modułów, których nie trzeba dotykać, dzięki czemu można cofnąć się i manipulować strukturą maszyny stanowej bez wpływu na napisany kod pomocniczy.

Przepraszam, jestem zbyt entuzjastyczny i bez wątpienia zniechęcający wszystkich. Ale jest to narzędzie najwyższej klasy i dobrze udokumentowane.

 29
Author: willw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 17:21:16

Koniecznie sprawdź pracę Miro Samka (blog State Space, website state Machines & Tools), którego artykuły w C/C++ Users Journal były świetne.

Strona zawiera kompletną (C/C++) implementację zarówno na licencji open source, jak i komercyjnej state machine framework (QP Framework) , event handler( QEP) , basic modeling tool (QM) oraz tracing tool (QSpy) {10]}, które pozwalają rysować maszyny stanowe, Utwórz kod i debuguj go.

Książka zawiera obszerne Wyjaśnienie na temat tego, co / dlaczego wdrożenia i jak z niego korzystać, a także jest świetnym materiałem do zrozumienia podstaw hierachicznych i skończonych maszyn państwowych.

Strona zawiera również linki do kilku pakietów wsparcia dla zarządów do korzystania z oprogramowania z platformami wbudowanymi.

 20
Author: Daniel Daranas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-09 07:18:11

Zrobiłem coś podobnego do tego, co opisuje paxdiablo, tylko zamiast tablicy przejść stanu/zdarzenia, ustawiłem 2-wymiarową tablicę wskaźników funkcji, z wartością zdarzenia jako indeks jednej osi i bieżącą wartością stanu jako drugą. Wtedy po prostu dzwonię state = state_table[event][state](params) i dzieje się coś dobrego. Komórki reprezentujące nieprawidłowe kombinacje stanu/zdarzenia otrzymują oczywiście wskaźnik do funkcji, która tak mówi.

Oczywiście działa to tylko wtedy, gdy wartości state i event są zarówno sąsiadujące zakresy i zaczynają się od 0 lub wystarczająco blisko.

 10
Author: ceo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 17:06:56

Bardzo ładną, opartą na szablonach maszynę stanową C++ "framework" podaje Stefan Heinzmann w swoim artykule.

Ponieważ w artykule nie ma linku do pełnego pobrania kodu, pozwoliłem sobie wkleić kod do projektu i sprawdzić go. Materiał poniżej jest testowany i zawiera kilka drobnych, ale dość oczywistych brakujących elementów.

Główną innowacją jest to, że kompilator generuje bardzo wydajny kod. Puste akcje wejścia/wyjścia nie mają żadnych kosztów. Niepuste akcje wejścia/wyjścia są inlinowane. Kompilator sprawdza również kompletność stanu. Brakujące akcje generują błędy łączenia. Jedyną rzeczą, która nie jest złapany jest brakujący Top::init.

Jest to bardzo fajna alternatywa dla implementacji Miro Samka, jeśli można żyć bez tego, czego brakuje -- jest to daleki od kompletnej implementacji UML Statechart, chociaż poprawnie implementuje semantykę UML, podczas gdy kod Samka z założenia nie obsługuje operacje wyjścia/przejścia / wejścia w prawidłowej kolejności.

Jeśli ten kod działa do tego, co musisz zrobić, a masz porządny kompilator C++ dla swojego systemu, prawdopodobnie będzie działał lepiej niż implementacja C/C++ Miro. Kompilator generuje spłaszczoną implementację o(1) transition state machine. Jeśli audyt wyników montażu potwierdzi, że optymalizacje działają zgodnie z życzeniem, osiągniesz bliskie teoretycznej wydajności. Najlepsza część: jest stosunkowo mała, łatwa do zrozumienia kod.

#ifndef HSM_HPP
#define HSM_HPP

// This code is from:
// Yet Another Hierarchical State Machine
// by Stefan Heinzmann
// Overload issue 64 december 2004
// http://accu.org/index.php/journals/252

/* This is a basic implementation of UML Statecharts.
 * The key observation is that the machine can only
 * be in a leaf state at any given time. The composite
 * states are only traversed, never final.
 * Only the leaf states are ever instantiated. The composite
 * states are only mechanisms used to generate code. They are
 * never instantiated.
 */

// Helpers

// A gadget from Herb Sutter's GotW #71 -- depends on SFINAE
template<class D, class B>
class IsDerivedFrom {
    class Yes { char a[1]; };
    class No  { char a[10]; };
    static Yes Test(B*); // undefined
    static No Test(...); // undefined
public:
    enum { Res = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) ? 1 : 0 };
};

template<bool> class Bool {};

// Top State, Composite State and Leaf State

template <typename H>
struct TopState {
    typedef H Host;
    typedef void Base;
    virtual void handler(Host&) const = 0;
    virtual unsigned getId() const = 0;
};

template <typename H, unsigned id, typename B>
struct CompState;

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct CompState : B {
    typedef B Base;
    typedef CompState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H>
struct CompState<H, 0, TopState<H> > : TopState<H> {
    typedef TopState<H> Base;
    typedef CompState<H, 0, Base> This;
    template <typename X> void handle(H&, const X&) const {}
    static void init(H&); // no implementation
    static void entry(H&) {}
    static void exit(H&) {}
};

template <typename H, unsigned id, typename B = CompState<H, 0, TopState<H> > >
struct LeafState : B {
    typedef H Host;
    typedef B Base;
    typedef LeafState<H, id, Base> This;
    template <typename X> void handle(H& h, const X& x) const { Base::handle(h, x); }
    virtual void handler(H& h) const { handle(h, *this); }
    virtual unsigned getId() const { return id; }
    static void init(H& h) { h.next(obj); } // don't specialize this
    static void entry(H&) {}
    static void exit(H&) {}
    static const LeafState obj; // only the leaf states have instances
};

template <typename H, unsigned id, typename B>
const LeafState<H, id, B> LeafState<H, id, B>::obj;

// Transition Object

template <typename C, typename S, typename T>
// Current, Source, Target
struct Tran {
    typedef typename C::Host Host;
    typedef typename C::Base CurrentBase;
    typedef typename S::Base SourceBase;
    typedef typename T::Base TargetBase;
    enum { // work out when to terminate template recursion
        eTB_CB = IsDerivedFrom<TargetBase, CurrentBase>::Res,
        eS_CB = IsDerivedFrom<S, CurrentBase>::Res,
        eS_C = IsDerivedFrom<S, C>::Res,
        eC_S = IsDerivedFrom<C, S>::Res,
        exitStop = eTB_CB && eS_C,
        entryStop = eS_C || eS_CB && !eC_S
    };
    // We use overloading to stop recursion.
    // The more natural template specialization
    // method would require to specialize the inner
    // template without specializing the outer one,
    // which is forbidden.
    static void exitActions(Host&, Bool<true>) {}
    static void exitActions(Host&h, Bool<false>) {
        C::exit(h);
        Tran<CurrentBase, S, T>::exitActions(h, Bool<exitStop>());
    }
    static void entryActions(Host&, Bool<true>) {}
    static void entryActions(Host& h, Bool<false>) {
        Tran<CurrentBase, S, T>::entryActions(h, Bool<entryStop>());
        C::entry(h);
    }
    Tran(Host & h) : host_(h) {
        exitActions(host_, Bool<false>());
    }
    ~Tran() {
        Tran<T, S, T>::entryActions(host_, Bool<false>());
        T::init(host_);
    }
    Host& host_;
};

// Initializer for Compound States

template <typename T>
struct Init {
    typedef typename T::Host Host;
    Init(Host& h) : host_(h) {}
    ~Init() {
        T::entry(host_);
        T::init(host_);
    }
    Host& host_;
};

#endif // HSM_HPP

Kod testu jest następujący.

#include <cstdio>
#include "hsm.hpp"
#include "hsmtest.hpp"

/* Implements the following state machine from Miro Samek's
 * Practical Statecharts in C/C++
 *
 * |-init-----------------------------------------------------|
 * |                           s0                             |
 * |----------------------------------------------------------|
 * |                                                          |
 * |    |-init-----------|        |-------------------------| |
 * |    |       s1       |---c--->|            s2           | |
 * |    |----------------|<--c----|-------------------------| |
 * |    |                |        |                         | |
 * |<-d-| |-init-------| |        | |-init----------------| | |
 * |    | |     s11    |<----f----| |          s21        | | |
 * | /--| |------------| |        | |---------------------| | |
 * | a  | |            | |        | |                     | | |
 * | \->| |            |------g--------->|-init------|    | | |
 * |    | |____________| |        | |-b->|    s211   |---g--->|
 * |    |----b---^       |------f------->|           |    | | |
 * |    |________________|        | |<-d-|___________|<--e----|
 * |                              | |_____________________| | |
 * |                              |_________________________| |
 * |__________________________________________________________|
 */

class TestHSM;

typedef CompState<TestHSM,0>     Top;
typedef CompState<TestHSM,1,Top>   S0;
typedef CompState<TestHSM,2,S0>      S1;
typedef LeafState<TestHSM,3,S1>        S11;
typedef CompState<TestHSM,4,S0>      S2;
typedef CompState<TestHSM,5,S2>        S21;
typedef LeafState<TestHSM,6,S21>         S211;

enum Signal { A_SIG, B_SIG, C_SIG, D_SIG, E_SIG, F_SIG, G_SIG, H_SIG };

class TestHSM {
public:
    TestHSM() { Top::init(*this); }
    ~TestHSM() {}
    void next(const TopState<TestHSM>& state) {
        state_ = &state;
    }
    Signal getSig() const { return sig_; }
    void dispatch(Signal sig) {
        sig_ = sig;
        state_->handler(*this);
    }
    void foo(int i) {
        foo_ = i;
    }
    int foo() const {
        return foo_;
    }
private:
    const TopState<TestHSM>* state_;
    Signal sig_;
    int foo_;
};

bool testDispatch(char c) {
    static TestHSM test;
    if (c<'a' || 'h'<c) {
        return false;
    }
    printf("Signal<-%c", c);
    test.dispatch((Signal)(c-'a'));
    printf("\n");
    return true;
}

int main(int, char**) {
    testDispatch('a');
    testDispatch('e');
    testDispatch('e');
    testDispatch('a');
    testDispatch('h');
    testDispatch('h');
    return 0;
}

#define HSMHANDLER(State) \
    template<> template<typename X> inline void State::handle(TestHSM& h, const X& x) const

HSMHANDLER(S0) {
    switch (h.getSig()) {
    case E_SIG: { Tran<X, This, S211> t(h);
        printf("s0-E;");
        return; }
    default:
        break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S1) {
    switch (h.getSig()) {
    case A_SIG: { Tran<X, This, S1> t(h);
        printf("s1-A;"); return; }
    case B_SIG: { Tran<X, This, S11> t(h);
        printf("s1-B;"); return; }
    case C_SIG: { Tran<X, This, S2> t(h);
        printf("s1-C;"); return; }
    case D_SIG: { Tran<X, This, S0> t(h);
        printf("s1-D;"); return; }
    case F_SIG: { Tran<X, This, S211> t(h);
        printf("s1-F;"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S11) {
    switch (h.getSig()) {
    case G_SIG: { Tran<X, This, S211> t(h);
        printf("s11-G;"); return; }
    case H_SIG: if (h.foo()) {
            printf("s11-H");
            h.foo(0); return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S2) {
    switch (h.getSig()) {
    case C_SIG: { Tran<X, This, S1> t(h);
        printf("s2-C"); return; }
    case F_SIG: { Tran<X, This, S11> t(h);
        printf("s2-F"); return; }
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S21) {
    switch (h.getSig()) {
    case B_SIG: { Tran<X, This, S211> t(h);
        printf("s21-B;"); return; }
    case H_SIG: if (!h.foo()) {
            Tran<X, This, S21> t(h);
            printf("s21-H;"); h.foo(1);
            return;
        } break;
    default: break;
    }
    return Base::handle(h, x);
}

HSMHANDLER(S211) {
    switch (h.getSig()) {
    case D_SIG: { Tran<X, This, S21> t(h);
        printf("s211-D;"); return; }
    case G_SIG: { Tran<X, This, S0> t(h);
        printf("s211-G;"); return; }
    }
    return Base::handle(h, x);
}

#define HSMENTRY(State) \
    template<> inline void State::entry(TestHSM&) { \
        printf(#State "-ENTRY;"); \
    }

HSMENTRY(S0)
HSMENTRY(S1)
HSMENTRY(S11)
HSMENTRY(S2)
HSMENTRY(S21)
HSMENTRY(S211)

#define HSMEXIT(State) \
    template<> inline void State::exit(TestHSM&) { \
        printf(#State "-EXIT;"); \
    }

HSMEXIT(S0)
HSMEXIT(S1)
HSMEXIT(S11)
HSMEXIT(S2)
HSMEXIT(S21)
HSMEXIT(S211)

#define HSMINIT(State, InitState) \
    template<> inline void State::init(TestHSM& h) { \
       Init<InitState> i(h); \
       printf(#State "-INIT;"); \
    }

HSMINIT(Top, S0)
HSMINIT(S0, S1)
HSMINIT(S1, S11)
HSMINIT(S2, S21)
HSMINIT(S21, S211)
 9
Author: Kuba Ober,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 12:29:32

Technika, którą lubię dla maszyn stanowych (przynajmniej tych do sterowania programem), polega na używaniu wskaźników funkcji. Każdy stan jest reprezentowany przez inną funkcję. Funkcja pobiera symbol wejściowy i zwraca wskaźnik funkcji dla następnego stanu. Central dispatch loop monitoruje kolejne wejście, przekazuje je do aktualnego stanu i przetwarza wynik.

Pisanie na nim staje się trochę dziwne, ponieważ C nie ma sposobu na wskazanie typów wskaźników funkcji zwracających siebie, więc funkcje stanu zwracają void*. Ale można zrobić coś takiego:

typedef void* (*state_handler)(input_symbol_t);
void dispatch_fsm()
{
    state_handler current = initial_handler;
    /* Let's assume returning null indicates end-of-machine */
    while (current) {
        current = current(get_input);
    }
 }

Następnie poszczególne funkcje stanu mogą włączyć swoje dane wejściowe do procesu i zwrócić odpowiednią wartość.

 5
Author: Michael Ekstrand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-23 01:07:55

Najprostszy przypadek

enum event_type { ET_THIS, ET_THAT };
union event_parm { uint8_t this; uint16_t that; }
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum { THIS, THAT } state;
  switch (state)
  {
    case THIS:
    switch (event)
    {
      case ET_THIS:
      // Handle event.
      break;

      default:
      // Unhandled events in this state.
      break;
    }
    break;

    case THAT:
    // Handle state.
    break;
  }
}

Punkty: Stan jest prywatny, nie tylko dla jednostki kompilacji, ale także dla event_handler. Szczególne przypadki mogą być rozpatrywane oddzielnie od głównego przełącznika przy użyciu dowolnej konstrukcji uznanej za konieczną.

Bardziej skomplikowany przypadek

Gdy przełącznik jest większy niż kilka pełnych ekranów, podziel go na funkcje obsługujące każdy stan, używając tabeli stanów do bezpośredniego wyszukiwania funkcji. Państwo jest nadal prywatne do zdarzenia handler. Funkcje obsługi stanu zwracają Następny stan. W razie potrzeby niektóre wydarzenia nadal mogą być traktowane jako specjalne w Main Event handler. Lubię dorzucać pseudo-zdarzenia dla wejścia i wyjścia stanu i być może startu maszyny stanowej:

enum state_type { THIS, THAT, FOO, NA };
enum event_type { ET_START, ET_ENTER, ET_EXIT, ET_THIS, ET_THAT, ET_WHATEVER, ET_TIMEOUT };
union event_parm { uint8_t this; uint16_t that; };
static void handle_event(enum event_type event, union event_parm parm)
{
  static enum state_type state;
  static void (* const state_handler[])(enum event_type event, union event_parm parm) = { handle_this, handle_that };
  enum state_type next_state = state_handler[state](event, parm);
  if (NA != next_state && state != next_state)
  {
    (void)state_handler[state](ET_EXIT, 0);
    state = next_state;
    (void)state_handler[state](ET_ENTER, 0);
  }
}

Nie jestem pewien, czy dobrze zrozumiałem składnię, zwłaszcza jeśli chodzi o tablicę wskaźników funkcji. Nie przepuściłem tego przez kompilator. Po przejrzeniu zauważyłem, że zapomniałem wyraźnie odrzucić Następny stan podczas obsługi pseudo zdarzeń (nawias (void) przed wywołaniem state_handler ()). To jest coś, co lubię robić, nawet jeśli Kompilatory akceptują pominięcie po cichu. Mówi on czytelnikom kodu, że "tak, rzeczywiście miałem na myśli wywołanie funkcji bez użycia zwracanej wartości" i może powstrzymać narzędzia analizy statycznej przed ostrzeganiem o tym. To może być idiosynkratyczne, ponieważ nie przypominam sobie, żebym widział, jak ktoś inny to robi.

Punkty: dodanie odrobiny złożoności (sprawdzenie, czy Następny stan jest inny z bieżącego), może uniknąć powielania kodu gdzie indziej, ponieważ funkcje obsługi stanu mogą korzystać z pseudo zdarzeń, które występują, gdy stan jest wprowadzany i opuszczany. Pamiętaj, że stan nie może się zmienić podczas obsługi pseudo zdarzeń, ponieważ wynik obsługi stanu jest odrzucany po tych zdarzeniach. Możesz oczywiście zmienić zachowanie.

Obsługa stanu wyglądałaby tak:

static enum state_type handle_this(enum event_type event, union event_parm parm)
{
  enum state_type next_state = NA;
  switch (event)
  {
    case ET_ENTER:
    // Start a timer to do whatever.
    // Do other stuff necessary when entering this state.
    break;

    case ET_WHATEVER:
    // Switch state.
    next_state = THAT;
    break;

    case ET_TIMEOUT:
    // Switch state.
    next_state = FOO;
    break;

    case ET_EXIT:
    // Stop the timer.
    // Generally clean up this state.
    break;
  }
  return next_state;
}

Więcej złożoności

Gdy jednostka kompilacji staje się zbyt duży (cokolwiek to jest, powinienem powiedzieć około 1000 linii), umieść każdy handler stanu w osobnym pliku. Gdy każda obsługa stanu staje się dłuższa niż kilka ekranów, podziel każde zdarzenie w osobną funkcję, podobną do sposobu, w jaki przełącznik stanu został podzielony. Możesz to zrobić na wiele sposobów, niezależnie od stanu lub używając wspólnej tabeli lub łącząc różne schematy. Niektóre z nich zostały tu pokryte przez innych. Posortuj tabele i użyj wyszukiwania binarnego, jeśli prędkość jest wymagania.

Programowanie Ogólne

Chciałbym, aby preprocesor zajmował się takimi kwestiami jak sortowanie tabel czy nawet generowanie maszyn stanowych z opisów, pozwalając na "pisanie programów o programach". Wydaje mi się, że właśnie do tego ludzie Boost wykorzystują szablony C++, ale uważam składnię za tajemniczą.

Tablice dwuwymiarowe

W przeszłości używałem tabel stanu/zdarzenia, ale muszę powiedzieć, że w najprostszych przypadkach nie znajdź je konieczne i wolę jasność i czytelność instrukcji switch, nawet jeśli rozszerza się o jeden Pełny ekran. W przypadku bardziej złożonych przypadków tabele szybko wymykają się spod kontroli, jak zauważyli inni. Idiomy, które tu prezentuję, pozwalają dodać mnóstwo zdarzeń i stanów, kiedy masz na to ochotę, bez konieczności utrzymywania tabeli pochłaniającej pamięć (nawet jeśli może to być pamięć programu).

Disclaimer

Specjalne potrzeby mogą uczynić te idiomy mniej użytecznymi, ale mam okazało się, że są bardzo jasne i łatwe do utrzymania.

 5
Author: Joe the Hamster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-25 07:07:14

Niezwykle nietestowany, ale przyjemny w kodowaniu, teraz w bardziej wyrafinowanej wersji niż moja oryginalna odpowiedź; aktualne wersje można znaleźć na mercurial.intuxication.org :

Sm.h

#ifndef SM_ARGS
#error "SM_ARGS undefined: " \
    "use '#define SM_ARGS (void)' to get an empty argument list"
#endif

#ifndef SM_STATES
#error "SM_STATES undefined: " \
    "you must provide a list of comma-separated states"
#endif

typedef void (*sm_state) SM_ARGS;
static const sm_state SM_STATES;

#define sm_transit(STATE) ((sm_state (*) SM_ARGS)STATE)

#define sm_def(NAME) \
    static sm_state NAME ## _fn SM_ARGS; \
    static const sm_state NAME = (sm_state)NAME ## _fn; \
    static sm_state NAME ## _fn SM_ARGS

Przykład.c

#include <stdio.h>

#define SM_ARGS (int i)
#define SM_STATES EVEN, ODD
#include "sm.h"

sm_def(EVEN)
{
    printf("even %i\n", i);
    return ODD;
}

sm_def(ODD)
{
    printf("odd  %i\n", i);
    return EVEN;
}

int main(void)
{
    int i = 0;
    sm_state state = EVEN;

    for(; i < 10; ++i)
        state = sm_transit(state)(i);

    return 0;
}
 4
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-16 13:22:58

Bardzo spodobała mi się odpowiedź paxdiable i postanowiłem zaimplementować wszystkie brakujące funkcje dla mojej aplikacji, takie jak zmienne straży i dane specyficzne dla maszyny stanowej.

Umieściłem swoją implementację na tej stronie, aby podzielić się nią ze społecznością. Został przetestowany przy użyciu IAR Embedded Workbench dla ARM.

Https://sourceforge.net/projects/compactfsm/

 4
Author: user108570,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-13 02:15:50

Kolejnym ciekawym narzędziem open source jest Yakindu Statechart Tools on statecharts.org . korzysta z Harel statecharts i tym samym dostarcza Stany hierarchiczne i równoległe oraz generuje kod C i C++ (a także Java). Nie korzysta z bibliotek, ale stosuje podejście "zwykłego kodu". Kod zasadniczo stosuje struktury typu switch-case. Generatory kodu można również dostosować. Dodatkowo narzędzie zapewnia wiele innych funkcji.

 4
Author: Axel T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-24 07:53:08

Dochodząc do tak późnego (jak zwykle) ale skanując dotychczasowe odpowiedzi uważam, że czegoś ważnego brakuje;

Odkryłem w moich własnych projektach, że bardzo pomocne może być NIE posiadanie funkcji dla każdej poprawnej kombinacji stanu/zdarzenia. Podoba mi się pomysł skutecznego posiadania tabeli 2D Stanów/wydarzeń. Ale podoba mi się, że elementy tabeli są czymś więcej niż prostym wskaźnikiem funkcji. Zamiast tego staram się zorganizować mój projekt tak, aby w jego sercu zawierał kilka prostych atomów elementy lub działania. W ten sposób mogę wymienić te proste pierwiastki atomowe na każdym przecięciu mojej tabeli stanu/zdarzenia. Chodzi o to, że nie musisz definiować masy n do kwadratu (zazwyczaj bardzo prostych) funkcji. Po co mieć coś tak podatnego na błędy, czasochłonnego, trudnego do napisania, trudnego do odczytania ?

Dołączam również opcjonalny nowy stan i opcjonalny wskaźnik funkcji dla każdej komórki w tabeli. Wskaźnik funkcji jest dostępny dla tych wyjątkowych przypadków, w których nie chcę po prostu odpalić listy akcji atomowych.

Wiesz, że robisz to dobrze, kiedy możesz wyrazić wiele różnych funkcji, po prostu edytując tabelę, bez nowego kodu do napisania.

 3
Author: Bill Forster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-23 01:38:54

Myślę, że moja jest trochę inna od wszystkich innych. trochę więcej oddzielenia kodu od danych niż widzę w innych odpowiedziach. Naprawdę przeczytałem teorię, aby to napisać, która implementuje pełny język regularny (bez wyrażeń regularnych, niestety). Ullman, Minsky, Chomsky. Nie mogę powiedzieć, że wszystko zrozumiałem, ale czerpałem ze starych mistrzów tak bezpośrednio, Jak to możliwe: poprzez ich słowa.

Używam wskaźnika funkcji do predykatu, który określa Przejście do stanu " tak " lub "nie". Ułatwia to tworzenie skończonego akceptora stanu dla języka regularnego, który programujesz w sposób bardziej podobny do języka asemblera. Proszę, nie zniechęcaj się moimi głupimi imionami. "czek" = = "czek". 'grok' = = [poszukaj w słowniku hakerów].

Więc dla każdej iteracji, czek wywołuje funkcję predykatu z bieżącym znakiem jako argumentem. Jeśli predykat zwraca true, znak jest używany (wskaźnik zaawansowany) i postępujemy zgodnie z Przejście 'y', aby wybrać następny stan. Jeśli predykat zwraca false, znak nie jest używany i podążamy za przejściem "n". Więc każda instrukcja jest dwukierunkową gałęzią! Czytałem wtedy historię Mela.

Ten kod pochodzi prosto z mojego interpretera postscriptowego i ewoluował do obecnej postaci z wieloma wskazówkami od kolegów z comp.lang.c. ponieważ postscript w zasadzie nie ma składni (wymaga tylko wyważonych nawiasów), język regularny Accepter w ten sposób działa również jako parser.

/* currentstr is set to the start of string by czek
   and used by setrad (called by israd) to set currentrad
   which is used by israddig to determine if the character
   in question is valid for the specified radix
   --
   a little semantic checking in the syntax!
 */
char *currentstr;
int currentrad;
void setrad(void) {
    char *end;
    currentrad = strtol(currentstr, &end, 10);
    if (*end != '#' /* just a sanity check,
                       the automaton should already have determined this */
    ||  currentrad > 36
    ||  currentrad < 2)
        fatal("bad radix"); /* should probably be a simple syntaxerror */
}

/*
   character classes
   used as tests by automatons under control of czek
 */
char *alpha = "0123456789" "ABCDE" "FGHIJ" "KLMNO" "PQRST" "UVWXYZ";
#define EQ(a,b) a==b
#define WITHIN(a,b) strchr(a,b)!=NULL
int israd  (int c) {
    if (EQ('#',c)) { setrad(); return true; }
    return false;
}
int israddig(int c) {
    return strchrnul(alpha,toupper(c))-alpha <= currentrad;
}
int isdot  (int c) {return EQ('.',c);}
int ise    (int c) {return WITHIN("eE",c);}
int issign (int c) {return WITHIN("+-",c);}
int isdel  (int c) {return WITHIN("()<>[]{}/%",c);}
int isreg  (int c) {return c!=EOF && !isspace(c) && !isdel(c);}
#undef WITHIN
#undef EQ

/*
   the automaton type
 */
typedef struct { int (*pred)(int); int y, n; } test;

/*
   automaton to match a simple decimal number
 */
/* /^[+-]?[0-9]+$/ */
test fsm_dec[] = {
/* 0*/ { issign,  1,  1 },
/* 1*/ { isdigit, 2, -1 },
/* 2*/ { isdigit, 2, -1 },
};
int acc_dec(int i) { return i==2; }

/*
   automaton to match a radix number
 */
/* /^[0-9]+[#][a-Z0-9]+$/ */
test fsm_rad[] = {
/* 0*/ { isdigit,  1, -1 },
/* 1*/ { isdigit,  1,  2 },
/* 2*/ { israd,    3, -1 },
/* 3*/ { israddig, 4, -1 },
/* 4*/ { israddig, 4, -1 },
};
int acc_rad(int i) { return i==4; }

/*
   automaton to match a real number
 */
/* /^[+-]?(d+(.d*)?)|(d*.d+)([eE][+-]?d+)?$/ */
/* represents the merge of these (simpler) expressions
   [+-]?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?
   [+-]?[0-9]*\.[0-9]+([eE][+-]?[0-9]+)?
   The complexity comes from ensuring at least one
   digit in the integer or the fraction with optional
   sign and optional optionally-signed exponent.
   So passing isdot in state 3 means at least one integer digit has been found
   but passing isdot in state 4 means we must find at least one fraction digit
   via state 5 or the whole thing is a bust.
 */
test fsm_real[] = {
/* 0*/ { issign,  1,   1 },
/* 1*/ { isdigit, 2,   4 },
/* 2*/ { isdigit, 2,   3 },
/* 3*/ { isdot,   6,   7 },
/* 4*/ { isdot,   5,  -1 },
/* 5*/ { isdigit, 6,  -1 },
/* 6*/ { isdigit, 6,   7 },
/* 7*/ { ise,     8,  -1 },
/* 8*/ { issign,  9,   9 },
/* 9*/ { isdigit, 10, -1 },
/*10*/ { isdigit, 10, -1 },
};
int acc_real(int i) {
    switch(i) {
        case 2: /* integer */
        case 6: /* real */
        case 10: /* real with exponent */
            return true;
    }
    return false;
}

/*
   Helper function for grok.
   Execute automaton against the buffer,
   applying test to each character:
       on success, consume character and follow 'y' transition.
       on failure, do not consume but follow 'n' transition.
   Call yes function to determine if the ending state
   is considered an acceptable final state.
   A transition to -1 represents rejection by the automaton
 */
int czek (char *s, test *fsm, int (*yes)(int)) {
    int sta = 0;
    currentstr = s;
    while (sta!=-1 && *s) {
        if (fsm[sta].pred((int)*s)) {
            sta=fsm[sta].y;
            s++;
        } else {
            sta=fsm[sta].n;
        }
    }
    return yes(sta);
}

/*
   Helper function for toke.
   Interpret the contents of the buffer,
   trying automatons to match number formats;
   and falling through to a switch for special characters.
   Any token consisting of all regular characters
   that cannot be interpreted as a number is an executable name
 */
object grok (state *st, char *s, int ns,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {

    if (czek(s, fsm_dec, acc_dec)) {
        long num;
        num = strtol(s,NULL,10);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MIN) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_rad, acc_rad)) {
        long ra,num;
        ra = (int)strtol(s,NULL,10);
        if (ra > 36 || ra < 2) {
            error(st,limitcheck);
        }
        num = strtol(strchr(s,'#')+1, NULL, (int)ra);
        if ((num==LONG_MAX || num==LONG_MIN) && errno==ERANGE) {
            error(st,limitcheck);
/*       } else if (num > INT_MAX || num < INT_MAX) { */
/*           error(limitcheck, OP_token); */
        } else {
            return consint(num);
        }
    }

    else if (czek(s, fsm_real, acc_real)) {
        double num;
        num = strtod(s,NULL);
        if ((num==HUGE_VAL || num==-HUGE_VAL) && errno==ERANGE) {
            error(st,limitcheck);
        } else {
            return consreal(num);
        }
    }

    else switch(*s) {
        case '(': {
            int c, defer=1;
            char *sp = s;

            while (defer && (c=next(st,src)) != EOF ) {
                switch(c) {
                    case '(': defer++; break;
                    case ')': defer--;
                        if (!defer) goto endstring;
                        break;
                    case '\\': c=next(st,src);
                        switch(c) {
                            case '\n': continue;
                            case 'a': c = '\a'; break;
                            case 'b': c = '\b'; break;
                            case 'f': c = '\f'; break;
                            case 'n': c = '\n'; break;
                            case 'r': c = '\r'; break;
                            case 't': c = '\t'; break;
                            case 'v': c = '\v'; break;
                            case '\'': case '\"':
                            case '(': case ')':
                            default: break;
                        }
                }
                if (sp-s>ns) error(st,limitcheck);
                else *sp++ = c;
            }
endstring:  *sp=0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '<': {
            int c;
            char d, *x = "0123456789abcdef", *sp = s;
            while (c=next(st,src), c!='>' && c!=EOF) {
                if (isspace(c)) continue;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d = (char)c << 4;
                while (isspace(c=next(st,src))) /*loop*/;
                if (isxdigit(c)) c = strchr(x,tolower(c)) - x;
                else error(st,syntaxerror);
                d |= (char)c;
                if (sp-s>ns) error(st,limitcheck);
                *sp++ = d;
            }
            *sp = 0;
            return cvlit(consstring(st,s,sp-s));
        }

        case '{': {
            object *a;
            size_t na = 100;
            size_t i;
            object proc;
            object fin;

            fin = consname(st,"}");
            (a = malloc(na * sizeof(object))) || (fatal("failure to malloc"),0);
            for (i=0 ; objcmp(st,a[i]=toke(st,src,next,back),fin) != 0; i++) {
                if (i == na-1)
                (a = realloc(a, (na+=100) * sizeof(object))) || (fatal("failure to malloc"),0);
            }
            proc = consarray(st,i);
            { size_t j;
                for (j=0; j<i; j++) {
                    a_put(st, proc, j, a[j]);
                }
            }
            free(a);
            return proc;
        }

        case '/': {
            s[1] = (char)next(st,src);
            puff(st, s+2, ns-2, src, next, back);
            if (s[1] == '/') {
                push(consname(st,s+2));
                opexec(st, op_cuts.load);
                return pop();
            }
            return cvlit(consname(st,s+1));
        }

        default: return consname(st,s);
    }
    return null; /* should be unreachable */
}

/*
   Helper function for toke.
   Read into buffer any regular characters.
   If we read one too many characters, put it back
   unless it's whitespace.
 */
int puff (state *st, char *buf, int nbuf,
    object *src,
    int (*next)(state *,object *),
    void (*back)(state *,int, object *)) {
    int c;
    char *s = buf;
    while (isreg(c=next(st,src))) {
        if (s-buf >= nbuf-1) return false;
        *s++ = c;
    }
    *s = 0;
    if (!isspace(c) && c != EOF) back(st,c,src); /* eat interstice */
    return true;
}

/*
   Helper function for Stoken Ftoken.
   Read a token from src using next and back.
   Loop until having read a bona-fide non-whitespace non-comment character.
   Call puff to read into buffer up to next delimiter or space.
   Call grok to figure out what it is.
 */
#define NBUF MAXLINE
object toke (state *st, object *src,
        int (*next)(state *, object *),
        void (*back)(state *, int, object *)) {
    char buf[NBUF] = "", *s=buf;
    int c,sta = 1;
    object o;

    do {
        c=next(st,src);
        //if (c==EOF) return null;
        if (c=='%') {
            if (DUMPCOMMENTS) fputc(c, stdout);
            do {
                c=next(st,src);
                if (DUMPCOMMENTS) fputc(c, stdout);
            } while (c!='\n' && c!='\f' && c!=EOF);
        }
    } while (c!=EOF && isspace(c));
    if (c==EOF) return null;
    *s++ = c;
    *s = 0;
    if (!isdel(c)) sta=puff(st, s,NBUF-1,src,next,back);

    if (sta) {
        o=grok(st,buf,NBUF-1,src,next,back);
        return o;
    } else {
        return null;
    }
}
 3
Author: luser droog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 03:32:01

Boost.org zawiera 2 różne implementacje wykresu stanu:

Jak zawsze, boost przeniesie cię do template hell.

Pierwsza Biblioteka jest przeznaczona dla maszyn stanowych o znaczeniu krytycznym. Druga biblioteka daje bezpośrednią ścieżkę przejścia z karty stanu UML do kodu.

Oto więc pytanie z prośbą o porównanie dwóch gdzie obaj autorzy odpowiedz.

 3
Author: Roland Wolf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:19

Ta Seria postów Ars OpenForum o nieco skomplikowanej logice sterowania zawiera bardzo łatwą do naśladowania implementację jako maszyny stanowej w C.

 2
Author: Steven Huwig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 02:23:50

Widziałem to gdzieś

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0)
      NEXTSTATE(y);
    else
      NEXTSTATE(x);
  }
}
 2
Author: pixelbeat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 22:40:47

Biorąc pod uwagę, że sugerujesz, że możesz używać C++ , a tym samym kodu OO, sugerowałbym ocenę wzorca ' Gof ' (Gof = Gang czterech, faceci, którzy napisali książkę wzorców projektowych, która wprowadziła wzorce projektowe w światło dzienne).

Nie jest to szczególnie skomplikowane i jest szeroko stosowane i omawiane, więc łatwo jest zobaczyć przykłady i wyjaśnienia on-line.

Będzie również prawdopodobnie rozpoznawalny przez każdego, kto utrzyma twój kod w późniejszym terminie.

Jeśli wydajność jest zmartwienie, warto byłoby rzeczywiście benchmarking, aby upewnić się, że podejście nie OO jest bardziej wydajne, ponieważ wiele czynników wpływa na wydajność i nie zawsze jest po prostu oo zły, funkcjonalny kod dobry. Podobnie, jeśli użycie pamięci jest dla Ciebie ograniczeniem, warto ponownie wykonać kilka testów lub obliczeń, aby sprawdzić, czy będzie to rzeczywiście problemem dla konkretnej aplikacji, jeśli użyjesz wzorca stanu.

Poniżej znajdują się linki do wzorca stanu "Gof", jako Craig sugeruje:

 2
Author: Mick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-27 20:54:12

Twoje pytanie jest dość ogólne,
Oto dwa artykuły referencyjne, które mogą być przydatne,

  1. Implementacja Embedded State Machine

    Ten artykuł opisuje proste podejście do implementacji maszyny stanowej dla systemu wbudowanego. Na potrzeby tego artykułu maszyna stanowa jest zdefiniowana jako algorytm, który może być w jednym z niewielu Stanów. Stan jest warunkiem, który powoduje określony związek wejść z wyjściami, a wejścia do kolejnych stanów.
    Doświadczony czytelnik szybko zauważy, że maszyny państwowe opisane w tym artykule są maszynami mięsnymi. Maszyna mealy 'ego jest maszyną stanową, w której wyjścia są funkcją zarówno stanu obecnego, jak i wejścia, w przeciwieństwie do maszyny Moore' a, w której wyjścia są funkcją tylko stanu.

    • kodowanie maszyn stanowych w C i C++

      Moim zajęciem w tym artykule są podstawy maszyn państwowych i kilka prostych wytyczne programistyczne do kodowania maszyn stanowych w C lub c++. Mam nadzieję, że te proste techniki mogą stać się bardziej powszechne, dzięki czemu Ty (i inni) możesz łatwo zobaczyć strukturę maszyny stanowej bezpośrednio z kodu źródłowego.

 1
Author: nik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-30 02:18:11

Używałem State Machine Compiler w projektach Java i Python z powodzeniem.

 1
Author: feeling abused and harassed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-20 18:41:14

To jest stary post z mnóstwem odpowiedzi, ale pomyślałem, że dodam własne podejście do maszyny skończonych stanów w C. zrobiłem skrypt Pythona, aby wyprodukować szkielet kodu C dla dowolnej liczby stanów. Ten skrypt jest udokumentowany na GituHub pod adresem FsmTemplateC

Ten przykład opiera się na innych podejściach, o których czytałem. Nie używa instrukcji goto ani switch, ale zamiast tego posiada funkcje przejścia w macierzy wskaźników(tabela wyszukiwania). Kod opiera się na dużej wielowierszowej makro inicjalizatora i funkcje C99 (wyznaczone inicjalizatory i litery złożone), więc jeśli nie lubisz tych rzeczy, może Ci się to nie spodobać.

Oto skrypt Pythona przykład kołowrotu, który generuje szkielet kodu C za pomocą FsmTemplateC :

# dict parameter for generating FSM
fsm_param = {
    # main FSM struct type string
    'type': 'FsmTurnstile',
    # struct type and name for passing data to state machine functions
    # by pointer (these custom names are optional)
    'fopts': {
        'type': 'FsmTurnstileFopts',
        'name': 'fopts'
    },
    # list of states
    'states': ['locked', 'unlocked'],
    # list of inputs (can be any length > 0)
    'inputs': ['coin', 'push'],
    # map inputs to commands (next desired state) using a transition table
    # index of array corresponds to 'inputs' array
    # for this example, index 0 is 'coin', index 1 is 'push'
    'transitiontable': {
        # current state |  'coin'  |  'push'  |
        'locked':       ['unlocked',        ''],
        'unlocked':     [        '',  'locked']
    }
}

# folder to contain generated code
folder = 'turnstile_example'
# function prefix
prefix = 'fsm_turnstile'

# generate FSM code
code = fsm.Fsm(fsm_param).genccode(folder, prefix)

Wygenerowany nagłówek wyjściowy zawiera typedefs:

/* function options (EDIT) */
typedef struct FsmTurnstileFopts {
    /* define your options struct here */
} FsmTurnstileFopts;

/* transition check */
typedef enum eFsmTurnstileCheck {
    EFSM_TURNSTILE_TR_RETREAT,
    EFSM_TURNSTILE_TR_ADVANCE,
    EFSM_TURNSTILE_TR_CONTINUE,
    EFSM_TURNSTILE_TR_BADINPUT
} eFsmTurnstileCheck;

/* states (enum) */
typedef enum eFsmTurnstileState {
    EFSM_TURNSTILE_ST_LOCKED,
    EFSM_TURNSTILE_ST_UNLOCKED,
    EFSM_TURNSTILE_NUM_STATES
} eFsmTurnstileState;

/* inputs (enum) */
typedef enum eFsmTurnstileInput {
    EFSM_TURNSTILE_IN_COIN,
    EFSM_TURNSTILE_IN_PUSH,
    EFSM_TURNSTILE_NUM_INPUTS,
    EFSM_TURNSTILE_NOINPUT
} eFsmTurnstileInput;

/* finite state machine struct */
typedef struct FsmTurnstile {
    eFsmTurnstileInput input;
    eFsmTurnstileCheck check;
    eFsmTurnstileState cur;
    eFsmTurnstileState cmd;
    eFsmTurnstileState **transition_table;
    void (***state_transitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
    void (*run)(struct FsmTurnstile *, FsmTurnstileFopts *, const eFsmTurnstileInput);
} FsmTurnstile;

/* transition functions */
typedef void (*pFsmTurnstileStateTransitions)(struct FsmTurnstile *, FsmTurnstileFopts *);
  • enum eFsmTurnstileCheck jest używane do określenia, czy przejście zostało zablokowane za pomocą EFSM_TURNSTILE_TR_RETREAT, czy może przejść za pomocą EFSM_TURNSTILE_TR_ADVANCE, lub wywołanie funkcji nie było poprzedzone przejściem z EFSM_TURNSTILE_TR_CONTINUE.
  • enum eFsmTurnstileState to po prostu lista stanów.
  • enum eFsmTurnstileInput jest po prostu listą danych wejściowych.
  • struktura FsmTurnstile jest sercem maszyny stanowej z sprawdzeniem przejścia, tabelą wyszukiwania funkcji, stanem bieżącym, stanem rozkazanym i aliasem do podstawowej funkcji, która uruchamia maszynę.
  • każdy wskaźnik funkcji (alias) w FsmTurnstile powinien być wywoływany tylko ze struktury i musi mieć swoje pierwsze wejście jako wskaźnik do siebie tak, aby utrzymać stan trwały, styl obiektowy.

Teraz dla deklaracji funkcji w nagłówku:

/* fsm declarations */
void fsm_turnstile_locked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_locked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_locked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_unlocked_unlocked (FsmTurnstile *fsm, FsmTurnstileFopts *fopts);
void fsm_turnstile_run (FsmTurnstile *fsm, FsmTurnstileFopts *fopts, const eFsmTurnstileInput input);

Nazwy funkcji są w formacie {prefix}_{from}_{to}, gdzie {from} jest poprzednim (bieżącym) stanem, a {to} jest następnym stanem. Zauważ, że jeśli tabela przejść nie pozwala na pewne przejścia, zostanie ustawiony wskaźnik NULL zamiast wskaźnika funkcji. Wreszcie magia dzieje się z makro. Tutaj budujemy tabelę przejściową (macierz liczb stanu) i funkcje przejścia stanu wyszukują tabelę (macierz wskaźników funkcji):

/* creation macro */
#define FSM_TURNSTILE_CREATE() \
{ \
    .input = EFSM_TURNSTILE_NOINPUT, \
    .check = EFSM_TURNSTILE_TR_CONTINUE, \
    .cur = EFSM_TURNSTILE_ST_LOCKED, \
    .cmd = EFSM_TURNSTILE_ST_LOCKED, \
    .transition_table = (eFsmTurnstileState * [EFSM_TURNSTILE_NUM_STATES]) { \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        }, \
        (eFsmTurnstileState [EFSM_TURNSTILE_NUM_INPUTS]) { \
            EFSM_TURNSTILE_ST_UNLOCKED, \
            EFSM_TURNSTILE_ST_LOCKED \
        } \
    }, \
    .state_transitions = (pFsmTurnstileStateTransitions * [EFSM_TURNSTILE_NUM_STATES]) { \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_locked_locked, \
            fsm_turnstile_locked_unlocked \
        }, \
        (pFsmTurnstileStateTransitions [EFSM_TURNSTILE_NUM_STATES]) { \
            fsm_turnstile_unlocked_locked, \
            fsm_turnstile_unlocked_unlocked \
        } \
    }, \
    .run = fsm_turnstile_run \
}

Podczas tworzenia FSM należy użyć makra FSM_EXAMPLE_CREATE().

Teraz w kodzie źródłowym powinna być wypełniona każda zadeklarowana powyżej funkcja przejścia stanu. Struktura FsmTurnstileFopts Może być używana do przekazywania danych do / Z maszyny stanowej. Każde przejście musi ustawić fsm->check na równe EFSM_EXAMPLE_TR_RETREAT, aby zablokować przejście lub EFSM_EXAMPLE_TR_ADVANCE, aby umożliwić przejście do / align = "left" / Przykład pracy można znaleźć na (FsmTemplateC) [ https://github.com/ChisholmKyle/FsmTemplateC].

Oto bardzo proste rzeczywiste użycie w Twoim kodzie:

/* create fsm */
FsmTurnstile fsm = FSM_TURNSTILE_CREATE();
/* create fopts */
FsmTurnstileFopts fopts = {
    .msg = ""
};
/* initialize input */
eFsmTurnstileInput input = EFSM_TURNSTILE_NOINPUT;

/* main loop */
for (;;) {
    /* wait for timer signal, inputs, interrupts, whatever */
    /* optionally set the input (my_input = EFSM_TURNSTILE_IN_PUSH for example) */
    /* run state machine */
    my_fsm.run(&my_fsm, &my_fopts, my_input);
}

Cała ta sprawa z nagłówkami i wszystkie te funkcje, aby mieć prosty i szybki interfejs, są tego warte w moim umyśle.

 1
Author: ChisholmKyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-05 17:58:04

Możesz użyć biblioteki open source OpenFST.

Openfst jest biblioteką do konstruowania, łączenia, optymalizacji i wyszukiwania ważonych przetworników stanu skończonego (FST). Ważone przetworniki stanu skończonego są automatami, w których każde przejście ma etykietę wejściową, Etykietę wyjściową i wagę. Bardziej znany akceptor stanu skończonego jest reprezentowany jako przetwornik z etykietą wejścia i wyjścia każdego przejścia równą. Akceptory stanu skończonego są używane do reprezentowania zbiorów ciągi (w szczególności, regularne lub racjonalne zbiory); przetworniki stanu skończonego są używane do reprezentowania relacji binarnych między parami ciągów (w szczególności, racjonalne przetworniki). Wagi mogą być używane do reprezentowania kosztów podjęcia określonego przejścia.
 0
Author: Vicky Chijwani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-23 21:44:19
void (* StateController)(void); 
void state1(void);
void state2(void);

void main()
{
 StateController=&state1;
 while(1)
 {
  (* StateController)();
 }
}

void state1(void)
{
 //do something in state1
 StateController=&state2;
}

void state2(void)
{
 //do something in state2
 //Keep changing function direction based on state transition
 StateController=&state1;
}
 0
Author: Akshay Immanuel D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-30 09:36:39

Ja osobiscie uzywam struktur samo-referujacych w polaczeniu z tablicami pointerowymi. Jakiś czas temu wgrałem tutorial na GitHubie, link:

Https://github.com/mmelchger/polling_state_machine_c

Uwaga: zdaję sobie sprawę, że ten wątek jest dość stary, ale mam nadzieję uzyskać wkład i przemyślenia na temat konstrukcji maszyny stanowej, a także być w stanie podać przykład możliwego projektu maszyny stanowej w C.

 0
Author: mmoment,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-13 09:55:03