Die Boost C++ Bibliotheken

Kapitel 68. Boost.MetaStateMachine

Boost.MetaStateMachine wird verwendet, um Zustandsautomaten zu definieren. Zustandsautomaten beschreiben Objekte über ihre Zustände. So legen sie fest, welche Zustände es gibt und von welchem Zustand in welchen anderen gewechselt werden kann.

Boost.MetaStateMachine bietet drei verschiedene Möglichkeiten an, Zustandsautomaten zu definieren. In der Dokumentation von Boost.MetaStateMachine ist von Frontends die Rede. Vom Frontend hängt es ab, welchen Code Sie schreiben müssen, um einen Zustandsautomaten zu erstellen.

Wenn Sie sich für das einfache Frontend oder das Funktor-Frontend entscheiden, definieren Sie Zustandsautomaten auf eine eher herkömmliche Art und Weise: Sie erstellen Klassen, leiten diese von Klassen ab, die Boost.MetaStateMachine zur Verfügung stellt, definieren erforderliche Eigenschaften und schreiben so den notwendigen C++-Code selbst. Der grundlegende Unterschied zwischem dem einfachen Frontend und dem Funktor-Frondend ist, dass Sie dort, wo Funktionen erwartet werden, im Funktor-Frontend Funktionsobjekte angeben können, während Sie im einfachen Frontend Funktionszeiger verwenden müssen.

Das dritte Frontend heißt eUML und basiert auf einer domainspezifischen Sprache. So können Sie mit diesem Frontend Zustandsautomaten definieren, indem Sie Definitionen aus Zustandsautomaten der UML entnehmen. Entwickler, die mit der UML vertraut sind, können Definitionen aus entsprechenden UML-Verhaltensdiagrammen in C++-Code übernehmen. Eine Übersetzung von UML-Definitionen in herkömmlichen C++-Code entfällt.

eUML basiert auf zahlreichen Makros, die Sie verwenden müssen, wenn Sie mit diesem Frontend arbeiten. Die Makros haben den Vorteil, dass Sie mit vielen Klassen, die Boost.MetaStateMachine verwendet und in Ihrem Code erwartet, nicht in Berührung kommen. Sie müssen zwar die entsprechenden Makros kennen. Dafür kann es nicht passieren, dass Sie wie beim einfachen Frontend oder dem Funktor-Frontend vergessen, Ihren Zustandsautomaten von einer bestimmten Klasse aus Boost.MetaStateMachine abzuleiten. Im Folgenden lernen Sie Boost.MetaStateMachine mit dem Frontend eUML kennen.

Beispiel 68.1. Ein einfacher Zustandsautomat mit eUML
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_STATE((), Off)
BOOST_MSM_EUML_STATE((), On)

BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press == On,
  On + press == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off),
light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  std::cout << *light.current_state() << '\n';
  light.process_event(press);
  std::cout << *light.current_state() << '\n';
  light.process_event(press);
  std::cout << *light.current_state() << '\n';
}

Beispiel 68.1 verwendet den einfachsten Zustandsautomaten, den es gibt: Eine Lampe kennt genau zwei Zustände. Sie ist entweder an oder aus. Ist sie aus, kann sie eingeschaltet werden. Ist sie an, kann sie ausgeschaltet werden. Es kann also von jedem Zustand in den anderen gewechselt werden.

Im Beispiel 68.1 wird das Frontend eUML verwendet, um den Zustandsautomaten einer Lampe zu beschreiben. Da es für Boost.MetaStateMachine keine Master-Headerdatei gibt, die alle notwendigen Definitionen zur Verfügung stellt, müssen Sie jeweils benötigte Headerdateien einzeln einbinden. So erhalten Sie über boost/msm/front/euml/euml.hpp und boost/msm/front/euml/state_grammar.hpp Zugriff auf Makros der eUML. boost/msm/back/state_machine.hpp müssen Sie einbinden, um einen Zustandsautomaten aus dem Frontend mit einem Zustandsautomaten aus dem Backend verbinden zu können. Während Frontends verschiedene Möglichkeiten zur Verfügung stellen, Zustandsautomaten zu beschreiben, ist im Backend die eigentliche Funktionalität enthalten, die Zustandsautomaten ausmacht. Es existiert lediglich ein Backend in Boost.MetaStateMachine, so dass Sie sich nicht wie bei Frontends für eine bestimmte Implementation entscheiden müssen.

Alle Definitionen von Boost.MetaStateMachine befinden sich im Namensraum boost::msm. Leider beziehen sich viele Makros der eUML nicht explizit auf Klassen in diesem Namensraum. Entweder wird auf den Namensraum msn Bezug genommen oder es werden Klassen ohne Angabe eines Namensraums verwendet. Sie müssen daher wie im Beispiel 68.1 geschehen die Namensräume boost::msn und boost::msm::front::euml besonders behandeln. Die Makros der eUML führen ansonsten zu Compilerfehlern.

Um den Zustandsautomaten einer Lampe zu verwenden, werden zuerst zwei Zustände für an und aus definiert. Sie verwenden dazu das Makro BOOST_MSM_EUML_STATE, dem Sie den Namen des Zustands als zweiten Parameter übergeben. Über den ersten Parameter können Sie den Zustand beschreiben. Sie erfahren später, welche Angaben Sie hier vornehmen können. Die beiden im Beispiel 68.1 definierten Zustände heißen Off und On.

Um zwischen Zuständen wechseln zu können, benötigen Sie Ereignisse. Sie verwenden dazu das Makro BOOST_MSM_EUML_EVENT, dem Sie ausschließlich den Namen des Ereignisses übergeben. Im Beispiel 68.1 wird ein Ereignis press definiert. Es symbolisiert das Drücken eines Lichtschalters. Da über das gleiche Ereignis ein Licht an- und ausgemacht werden kann, wird im Code lediglich ein Ereignis definiert.

Haben Sie die benötigten Zustände und Ereignisse definiert, greifen Sie auf das Makro BOOST_MSM_EUML_TRANSITION_TABLE zu, um eine Zustandsübergangstabelle zu erstellen. Diese beschreibt, von welchem Zustand in welchen anderen gewechselt wird, wenn ein bestimmtes Ereignis eintritt.

BOOST_MSM_EUML_TRANSITION_TABLE erwartet zwei Parameter, von denen der zweite der Name der Zustandsübergangstabelle ist. Der erste Parameter definiert die Zustandsübergangstabelle. Die Syntax des ersten Parameters ist so gestaltet, dass Sie auf einen Blick erkennen sollten, wie Zustände und Ereignisse zusammenhängen. So bedeutet im Beispiel 68.1 Off + press == On, dass der Automat im Zustand Off bei Eintreten des Ereignisses press in den Zustand On wechselt. Das Frontend eUML spielt an dieser Stelle seine Stärken aus, da eine eingängige und selbsterklärende Syntax zur Definition der Zustandsübergangstabelle verwendet werden kann.

Nachdem Sie die Zustandsübergangstabelle definiert haben, definieren Sie den Zustandsautomaten. Sie verwenden dazu das Makro BOOST_MSM_EUML_DECLARE_STATE_MACHINE. Der zweite Parameter ist wieder der einfachere: Er legt den Namen des Zustandsautomaten fest. Der Zustandsautomat im Beispiel 68.1 heißt light_state_machine.

Der erste Parameter von BOOST_MSM_EUML_DECLARE_STATE_MACHINE ist ein Tuple. Als erster Wert wird der Name der Zustandsübergangstabelle angegeben. Der zweite Wert ist ein Ausdruck, in dem auf ein Attribut init_ zugegriffen wird, das von Boost.MetaStateMachine zur Verfügung gestellt wird. Was es mit Attributen auf sich hat, erfahren Sie später. Der Ausdruck init_ << Off ist notwendig, um den Anfangszustand des Automaten auf Off zu setzen.

Bei dem mit BOOST_MSM_EUML_DECLARE_STATE_MACHINE definierten Zustandsautomaten light_state_machine handelt es sich um eine Klasse. Sie müssen die Klasse verwenden, um einen Zustandsautomaten aus dem Backend zu instanziieren. Im Beispiel 68.1 wird dazu auf die Template-Klasse boost::msm::back::state_machine zugegriffen und ihr als Parameter light_state_machine übergeben. Sie erhalten nun eine Instanz des Zustandsautomaten namens light.

Zustandsautomaten bieten mit process_event() eine Methode an, die Ereignisse verarbeiten kann. Sie übergeben process_event() als einzigen Parameter ein Ereignis, woraufhin der Automat abhängig von seiner Zustandsübergangstabelle den Zustand wechselt.

Damit Sie nachverfolgen können, was im Beispiel 68.1 passiert, wenn mehrfach process_event() aufgerufen wird, wird außerdem auf current_state() zugegriffen. Diese Methode sollten Sie ausschließlich zu Debuggingzwecken verwenden. Sie gibt einen Zeiger auf ein int zurück. Jedem Zustand ist eine Zahl zugewiesen, und zwar in der Reihenfolge, in der auf Zustände innerhalb von BOOST_MSM_EUML_TRANSITION_TABLE zugegriffen wird. Im Beispiel 68.1 ist entsprechend Off 0 und On 1 zugewiesen. Wenn Sie das Beispiel ausführen, wird 0, 1 und 0 auf die Standardausgabe ausgegeben. Zuerst ist das Licht aus. Dann wird der Lichtschalter zweimal betätigt, woraufhin das Licht an und wieder aus geht.

Beispiel 68.2. Ein Zustandsautomat mit Status, Ereignis und Aktion
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_STATE((), Off)
BOOST_MSM_EUML_STATE((), On)

BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_ACTION(switch_light)
{
  template <class Event, class Fsm>
  void operator()(const Event &ev, Fsm &fsm,
    BOOST_MSM_EUML_STATE_NAME(Off) &sourceState,
    BOOST_MSM_EUML_STATE_NAME(On) &targetState) const
  {
    std::cout << "Switching on\n";
  }

  template <class Event, class Fsm>
  void operator()(const Event &ev, Fsm &fsm,
    decltype(On) &sourceState,
    decltype(Off) &targetState) const
  {
    std::cout << "Switching off\n";
  }
};

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press / switch_light == On,
  On + press / switch_light == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off),
light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  light.process_event(press);
  light.process_event(press);
}

Beispiel 68.2 erweitert den Zustandsautomaten für die Lampe um eine Aktion. Eine Aktion wird ausgeführt, wenn ein Ereignis eintritt und ein Zustandswechsel erfolgt. Da Aktionen optional sind, konnte der Zustandsautomat im vorherigen Beispiel ohne sie auskommen.

Sie definieren eine Aktion mit BOOST_MSM_EUML_ACTION. Genaugenommen definieren Sie ein Funktionsobjekt. Sie müssen daher den Operator operator() überladen. Der Operator muss vier Parameter akzeptieren, bei denen es sich um Referenzen auf ein Ereignis, einen Zustandsautomaten und zwei Zustände handeln muss. Ob Sie ein Template definieren oder für alle vier Parameter konkrete Typen angeben, bleibt Ihnen überlassen. Im Beispiel 68.2 sind lediglich für die letzten beiden Parameter konkrete Typen angegeben. Da diese Parameter beschreiben, von welchem Zustand in welchen anderen gewechselt wird, können Sie operator() so überladen, dass unterschiedliche Methoden für unterschiedliche Wechsel aufgerufen werden.

Beachten Sie, dass es sich bei den Zuständen On und Off um Objekte handelt. Boost.MetaStateMachine bietet mit BOOST_MSM_EUML_STATE_NAME ein Makro an, um den Typ von Zustandsobjekten zu erhalten. Arbeiten Sie mit C++11, können Sie anstelle des Makros den Operator decltype verwenden.

Die mit BOOST_MSM_EUML_ACTION definierte Aktion switch_light soll ausgeführt werden, wenn der Lichtschalter betätigt wird. Dazu wurde die Zustandsübergangstabelle geändert. Die erste Angabe lautet nun Off + press / switch_light == On. Sie geben Aktionen hinter einem Schrägstrich nach dem Ereignis an. Diese Angabe bedeutet, dass der Operator operator() von switch_light aufgerufen wird, wenn sich der Automat im Zustand Off befindet und das Ereignis press eintritt. Nachdem die Aktion ausgeführt wurde, befindet sich der Automat im Zustand On.

Wenn Sie Beispiel 68.2 ausführen, wird zuerst Switching on und dann Switching off auf die Standardausgabe ausgegeben.

Beispiel 68.3. Ein Zustandsautomat mit Status, Ereignis, Guard und Aktion
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_STATE((), Off)
BOOST_MSM_EUML_STATE((), On)

BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_ACTION(is_broken)
{
  template <class Event, class Fsm, class Source, class Target>
  bool operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const
  {
    return true;
  }
};

BOOST_MSM_EUML_ACTION(switch_light)
{
  template <class Event, class Fsm, class Source, class Target>
  void operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const
  {
    std::cout << "Switching\n";
  }
};

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press [!is_broken] / switch_light == On,
  On + press / switch_light == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off),
light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  light.process_event(press);
  light.process_event(press);
}

Im Beispiel 68.3 wird in der Zustandsübergangstabelle ein Guard verwendet. Die erste Zeile lautet Off + press [!is_broken] / switch_light == On. Die Angabe von is_broken in eckigen Klammern bewirkt, dass vor dem Aufruf der Aktion switch_light überprüft wird, ob der Zustandswechsel erfolgen darf. Dies wird als Guard bezeichnet. Dieser muss ein Ergebnis vom Typ bool zurückgeben.

Ein Guard wie is_broken wird wie Aktionen mit dem Makro BOOST_MSM_EUML_ACTION definiert. Es muss demnach auch der Operator operator() überladen werden, der die vier bekannten Parameter erwartet. Wichtig ist, dass operator() einen Rückgabewert vom Typ bool hat. Ist dies der Fall, kann die Aktion als Guard verwendet werden.

Beachten Sie, dass Sie logische Operatoren wie operator! verwenden dürfen, wenn Sie einen Guard in eckigen Klammern angeben.

Wenn Sie das Beispiel ausführen, sehen Sie, dass keine Ausgabe auf die Standardausgabe erfolgt. Die Aktion switch_light wird nicht ausgeführt – das Licht bleibt aus. Der Guard is_broken gibt zwar true zurück. Aus dem true wird jedoch aufgrund des Operators operator! ein false.

Sie können Guards verwenden, um zu überprüfen, ob ein Zustandswechsel erfolgen darf. Im Beispiel 68.3 soll mit is_broken angegeben werden, dass die Lampe kaputt ist. Auch wenn grundsätzlich ein Wechsel von aus zu an möglich ist und der Zustandsautomat Lampen richtig beschreibt, kann die im Beispiel verwendete Lampe nicht eingeschaltet werden. Der Zustand der Lampe light ist trotz zweimaligem Aufruf von process_event() Off.

Beispiel 68.4. Ein Zustandsautomat mit Status, Ereignis und Ein- und Austrittsaktion
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_ACTION(state_entry)
{
  template <class Event, class Fsm, class State>
  void operator()(const Event &ev, Fsm &fsm, State &state) const
  {
    std::cout << "Entering\n";
  }
};

BOOST_MSM_EUML_ACTION(state_exit)
{
  template <class Event, class Fsm, class State>
  void operator()(const Event &ev, Fsm &fsm, State &state) const
  {
    std::cout << "Exiting\n";
  }
};

BOOST_MSM_EUML_STATE((state_entry, state_exit), Off)
BOOST_MSM_EUML_STATE((state_entry, state_exit), On)

BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press == On,
  On + press == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off),
light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  light.process_event(press);
  light.process_event(press);
}

Im Beispiel 68.4 wird als erster Parameter an BOOST_MSM_EUML_STATE ein Tuple bestehend aus state_entry und state_exit übergeben. Es handelt sich hierbei um Ein- und Austrittsaktionen. Die erste Angabe ist die Eintrittsaktion, die zweite die Austrittsaktion. Die Aktionen werden ausgeführt, wenn in einen Zustand gewechselt wird oder wenn ein Zustand verlassen wird.

Ein- und Austrittsaktionen werden wie Aktionen mit dem Makro BOOST_MSM_EUML_ACTION definiert. Der überladene Operator operator() erwartet jedoch mit einer Referenz auf ein Ereignis, einen Zustandsautomaten und einen Zustand lediglich drei Parameter. Da bei Ein- und Austrittsaktionen nicht der Übergang von einem Zustand zu einem anderen zählt, wird an den überladenen Operator operator() lediglich ein Zustand übergeben. Für Eintrittsaktionen ist das der Zustand, zu dem gewechselt wird, für Austrittsaktionen der Zustand, der verlassen wird.

Im Beispiel 68.4 besitzen beide Zustände Off und On Ein- und Austrittsaktionen. Da das Ereignis press zweimal eintritt, wird zweimal Entering und Exiting ausgegeben. Beachten Sie, dass zuerst Exiting und dann Entering ausgegeben wird. Die erste Aktion, die ausgeführt wird, ist eine Austrittsaktion.

Beim ersten Ereignis press wird vom Ursprungszustand Off zu On gewechselt. Für diesen Zustandswechsel wird einmal Exiting und einmal Entering ausgegeben. Beim zweiten Ereignis press wird von On zu Off gewechselt. Es wird wiederum einmal Exiting und einmal Entering ausgegeben. Bei einem Zustandswechel wird zuerst die Austrittsaktion des alten Zustands und dann die Eintrittsaktion des neuen Zustands ausgeführt.

Beispiel 68.5. Attribute in Zustandsautomaten
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_DECLARE_ATTRIBUTE(int, switched_on)

BOOST_MSM_EUML_ACTION(state_entry)
{
  template <class Event, class Fsm, class State>
  void operator()(const Event &ev, Fsm &fsm, State &state) const
  {
    std::cout << "Switched on\n";
    ++fsm.get_attribute(switched_on);
  }
};
BOOST_MSM_EUML_ACTION(is_broken)
{
  template <class Event, class Fsm, class Source, class Target>
  bool operator()(const Event &ev, Fsm &fsm, Source &src, Target &trg) const
  {
    return fsm.get_attribute(switched_on) > 1;
  }
};

BOOST_MSM_EUML_STATE((), Off)
BOOST_MSM_EUML_STATE((state_entry), On)
BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press [!is_broken] == On,
  On + press == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off, no_action, no_action,
attributes_ << switched_on), light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
}

Im Beispiel 68.5 wird der bereits bekannte Guard is_broken eingesetzt, um zu überprüfen, ob ein Zustandswechsel von Off zu On möglich ist. Diesmal hängt der Rückgabewert von is_broken davon ab, wie oft der Lichtschalter betätigt wurde. So soll die Lampe zweimal eingeschaltet werden können, bevor sie kaputt geht. Um mitzuzählen, wie oft die Lampe eingeschaltet wurde, wird ein Attribut verwendet.

Attribute sind Variablen, die Objekten hinzugefügt werden können. Sie sind wichtig, um das Verhalten von Zustandsautomaten zur Laufzeit zu verändern. Da Daten wie beispielsweise die Anzahl der Einschaltvorgänge irgendwo gespeichert werden können müssen, bietet es sich an, sie direkt im Zustandsautomaten, einem Zustand oder einem Ereignis zu speichern.

Bevor ein Attribut verwendet werden kann, muss es definiert werden. Dies geschieht mit dem Makro BOOST_MSM_EUML_DECLARE_ATTRIBUTE. Der erste Parameter, der an BOOST_MSM_EUML_DECLARE_ATTRIBUTE übergeben wird, ist der Typ, der zweite Parameter der Name des Attributs. Im Beispiel 68.5 wird auf diese Weise ein Attribut switched_on vom Typ int definiert.

Nachdem ein Attribut definiert wurde, muss es einem Objekt hinzugefügt werden. Im Beispiel wird das Attribut switched_on dem Zustandsautomaten hinzugefügt. Dies geschieht über den fünften Wert im Tuple, der als erster Parameter an BOOST_MSM_EUML_DECLARE_STATE_MACHINE übergeben wird. Dabei wird mit attributes_ auf ein von Boost.MetaStateMachine zur Verfügung gestelltes Schlüsselwort zugegriffen, um eine Lambda-Funktion zu erstellen. Indem attributes_ wie ein Stream verwendet wird und switched_on über den Operator operator<< an attributes_ übergeben wird, wird switched_on als Attribut dem Zustandsautomaten hinzugefügt.

Beachten Sie, dass no_action als dritter und vierter Wert im Tuple angegeben ist. Dies ist notwendig, um als fünfte Angabe im Tuple das Attribut hinzufügen zu können. Der dritte und vierte Wert können verwendet werden, um Ein- und Austrittsaktionen für einen Zustandsautomaten zu definieren. Sollen keine Ein- und Austrittsaktionen verwendet werden, muss auf no_action zugegriffen werden.

Nachdem das Attribut dem Zustandsautomaten hinzugefügt wurde, kann mit get_attribute() auf das Attribut zugegriffen werden. So wird diese Methode im Beispiel 68.5 in der Eintrittsaktion state_entry aufgerufen, um den Wert des Attributs um eins zu erhöhen. Da state_entry nur mit dem Zustand On verbunden ist, wird switched_on immer dann um eins erhöht, wenn das Licht eingeschaltet wird.

Neben state_entry wird außerdem im Guard is_broken auf switched_on zugegriffen. is_broken überprüfen, ob der Wert des Attributs größer als 1 ist. Ist dies der Fall, gibt der Guard true zurück. Da Attribute mit dem Standardkonstruktor initialisiert werden und switched_on auf 0 gesetzt ist, gibt is_broken dann true zurück, wenn das Licht zweimal eingeschaltet wurde.

Wenn Sie Beispiel 68.5 ausführen, tritt fünfmal das Ereignis press ein. Das Licht wird zweimal ein- und ausgeschaltet und anschließend wieder eingeschaltet. Für die ersten beiden Einschaltvorgänge wird Switched on ausgegeben. Beim dritten Einschalten findet jedoch keine Ausgabe statt. Da die Ausgabe in der Eintrittsaktion des Zustands On erfolgt, nach einem zweimaligen Einschalten is_broken jedoch true zurückgibt, findet kein Zustandswechsel mehr von Off zu On statt. Dementsprechend wird beim dritten Einschalten die Eintrittsaktion nicht ausgeführt.

Beispiel 68.6. Zugriff auf Attribute in Zustandsübergangstabellen
#include <boost/msm/front/euml/euml.hpp>
#include <boost/msm/front/euml/state_grammar.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <iostream>

namespace msm = boost::msm;
using namespace boost::msm::front::euml;

BOOST_MSM_EUML_DECLARE_ATTRIBUTE(int, switched_on)

void write_message()
{
  std::cout << "Switched on\n";
}

BOOST_MSM_EUML_FUNCTION(WriteMessage_, write_message, write_message_,
  void, void)

BOOST_MSM_EUML_STATE((), Off)
BOOST_MSM_EUML_STATE((), On)

BOOST_MSM_EUML_EVENT(press)

BOOST_MSM_EUML_TRANSITION_TABLE((
  Off + press [fsm_(switched_on) < Int_<2>()] / (++fsm_(switched_on),
    write_message_()) == On,
  On + press == Off
), light_transition_table)

BOOST_MSM_EUML_DECLARE_STATE_MACHINE(
(light_transition_table, init_ << Off, no_action, no_action,
attributes_ << switched_on), light_state_machine)

int main()
{
  msm::back::state_machine<light_state_machine> light;
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
  light.process_event(press);
}

Beispiel 68.6 macht das Gleiche wie das vorherige: Nach zweimaligem Einschalten ist die Lampe kaputt und kann kein weiteres Mal eingeschaltet werden. Während im vorherigen Beispiel auf das Attribut switched_on in Aktionen zugegriffen wurde, findet der Zugriff in diesem Beispiel direkt in der Zustandsübergangstabelle statt.

Boost.MetaStateMachine bietet die Funktion fsm_() an, um auf ein Attribut in einem Zustandsautomaten zuzugreifen. So wird auf diese Weise ein Guard definiert, der überprüft, ob switched_on kleiner als 2 ist. Außerdem wird eine Aktion definiert, die switched_on jedes Mal um eins erhöht, wenn ein Zustandswechsel von Off zu On stattfindet.

Beachten Sie, dass der Kleiner-als-Vergleich im Guard mit Int_<2>() stattfindet. Die Zahl 2 muss als Template-Parameter an Int_ übergeben werden, um dann eine Instanz dieser Klasse zu erstellen. Auf diese Weise wird ein Funktionsobjekt erstellt, das den richtigen Typ hat, damit Boost.MetaStateMachine erkennt, dass ein Kleiner-als-Vergleich mit 2 durchgeführt werden soll.

Im Beispiel 68.6 kommt mit BOOST_MSM_EUML_FUNCTION außerdem ein Makro zum Einsatz, um eine Funktion als Aktion zu definieren. Dem Makro wird als erster Parameter der Name der Aktion übergeben, mit dem sie im Funktion-Frontend eingesetzt werden kann. Der zweite Parameter ist der Name der Funktion. Der dritte Parameter ist der Name der Aktion, mit dem sie in der eUML eingesetzt wird. Der vierte und fünfte Parameter sind der Rückgabewert der Funktion – einmal für den Fall, dass die Aktion für einen Zustandsübergang eingesetzt wird, einmal für den Fall, dass die Aktion einen Ein- oder Austritt beschreibt. Nachdem write_message() auf diese Weise in eine Aktion umgewandelt wurde, wird ein Objekt vom Typ write_message_ erstellt und dieses hinter ++fsm_(switched_on) in der Zustandsübergangstabelle angegeben. Bei einem Übergang von Off zu On wird zuerst das Attribut switched_on inkrementiert und dann write_message() aufgerufen.

Wenn Sie Beispiel 68.6 ausführen, wird wie im vorherigen Beispiel Switched on genau zweimal ausgegeben.

Boost.MetaStateMachine bietet neben fsm_() weitere Funktionen wie state_() oder event_() an, um auf Attribute zuzugreifen, die anderen Objekten hinzugefügt wurden. Mit Char_ und String_ stehen weitere Klassen zur Verfügung, die ähnlich wie Int_ eingesetzt werden können.

Tipp

Das Frontend eUML verlangt wie in den Beispielen zu sehen den Einsatz vieler Makros. Die Headerdatei boost/msm/front/euml/common.hpp gibt einen guten Überblick über diese – dort finden Sie die Definition aller eUML-Makros.

Aufgaben

  1. Entwickeln Sie einen Zustandsautomaten für ein Fenster, das geschlossen, geöffnet oder gekippt sein kann. Vom geschlossenen Zustand kann sowohl in den geöffneten als auch in den gekippten Zustand gewechselt werden. Ein geöffnetes Fenster kann jedoch nicht gekippt werden, ohne es zuerst zu schließen. Noch kann ein gekipptes Fenster geöffnet werden, ohne es zuerst zu schließen. Testen Sie Ihren Zustandsautomaten, indem Sie Ihr Fenster mehrmals öffnen und kippen. Verwenden Sie current_state(), um den Zustand des Fensters jeweils in die Standardausgabe zu schreiben.

  2. Erweitern Sie Ihren Zustandsautomaten wie folgt: Ihr Fenster soll Teil eines intelligenten Hauses werden. Daher soll Ihr Zustandsautomat mitzählen, wie oft das Fenster geöffnet und gekippt wurde. Um Ihren Zustandsautomaten zu testen, öffnen und kippen Sie Ihr Fenster mehrmals. Geben Sie am Ende Ihres Programms aus, wie oft das Fenster geöffnet und gekippt wurde.