Friday 16 December 2011

Finite State Machine in C++

In my article "Finite State Machine in C" I gave a short introduction to Finite State Machines (FSM) and demonstrated two possible implementations of the Car state machine model.

In this article I will show C++ implementation of the same model, by using State Pattern. All state classes are derived from an abstract base class CState and each of them implements public method HandleEvent(EVENT evt). CSMManager class is a State Machine Manager and represents the core of our State Machine: it receives events and dispatches them to the current state for handling. Its private member m_pCurrState is a pointer to the CState base class but it always points to actual state objects. State transition is implemented as its reassignment to a different state object (object's address). This happens when some event occurs. Behaviour of this model is event-driven and, in contrast to C implementations, state transition control is not centralized here - it is not the State Manager who takes care of which state is going to be the next one. Current state decides on that itself, depending on its current conditions and events it receives.

Events.h:



States.h:



State.h:



StateTurnedOff.h:



StateTurnedOff.cpp:



StateIdle.h:



StateIdle.cpp:



StateMoving.h:



StateMoving.cpp:



StateInvalid.h:



StateInvalid.cpp:



SMManager.h:



SMManager.cpp:



main.cpp:



Output:


CStateTurnedOff::OnEntry()
Event received: EVENT_IGNITE
   Whoooa! I'm turned on!
CStateTurnedOff::OnExit()

CStateIdle::OnEntry()
Event received: EVENT_ACCELERATE
   Yipee! I'm accelerating!
CStateIdle::OnExit()

CStateMoving::OnEntry()
Event received: EVENT_BRAKE
   Whoops! Was I too fast?
CStateMoving::OnExit()

CStateIdle::OnEntry()
Event received: EVENT_TURN_OFF
   That was probably enough...
CStateIdle::OnExit()

CStateTurnedOff::OnEntry()
CStateTurnedOff::OnExit()


Note that State Manager's member which refers to the current state (m_pCurrState) is not of reference type (CState&) but a pointer (CState*). This is one of the cases where we MUST use pointer instead of reference because we want to have a variable which reffers to different objects throughout the execution and as re-seating the reference is not allowed, the only option is using a pointer. Please refer Parashift's FAQ on References and this SO question.

The reason for introducing m_prevStateID is that sometimes state machine (or some of its states) needs to know what was its previous state. Variable which keeps track of the previous state should not be of type reference (m_prevState : CState&) or pointer (m_pPrevState : CState*) as current state should not be able to access (members) of other states. It is therefore enough if it holds only the ID of the previous state.

Note that base abstract class CState declares OnEntry() and OnExit() methods - those which are executed when entering and leaving state. Current state executes HandleEvent(EVENT evt) each time it receives some event.

3 comments:

Unknown said...

Nice post. Thanks

Unknown said...

Nice post. Thanks

sapide said...

Among many solutions found on the web using state pattern, this is the most balanced approach so far. Did you also managed to make a unit test for this? I would think of something like log file comparison or something like that. Well done!