C++基础之状态机

本文介绍了C++中一种简洁的状态机实现,适用于嵌入式和PC平台。文章提供了更新的功能,但代码略有增加。该状态机设计紧凑,适用于各种平台,并具有事件驱动、类型安全和线程安全等特性。
摘要由CSDN通过智能技术生成

StateMachine sample code   

https://download.csdn.net/download/qccz123456/10567880

 

Doc:https://download.csdn.net/download/qccz123456/10567668

PDF:https://download.csdn.net/download/qccz123456/10567664

State Machine Design in C++

A compact C++ finite state machine (FSM) implementation that's easy to use on embedded and PC-based systems.

https://www.codeproject.com/Articles/1087619/State-Machine-Design-in-Cplusplus

Introduction 

In 2000, I wrote an article entitled "State Machine Design in C++" for C/C++ Users Journal (R.I.P.). Interestingly, that old article is still available and (at the time of writing this article) the #1 hit on Google when searching for C++ state machine. The article was written over 15 years ago, but I continue to use the basic idea on numerous projects. It's compact, easy to understand and, in most cases, has just enough features to accomplish what I need. 

This article provides a new implementation with updated features at the slight expense of a bit more code. I'll also correct the errors in the original implementation because if you're really tight on storage it’s still be a viable solution. Both designs are table driven suitable for any platform, embedded or PC, with any C++ compiler. 

Why another state machine design? Certainly by now there's an existing implementation out there that can be used, right? Maybe. On occasion, I'll try a new state machine and find it doesn't fit my needs for one reason or another. For me, the problem usually boils down to one or more of the following:

  1. Too large – the resulting implementation takes too much code space to justify on an embedded platform.
  2. Too complex – excessive templates or requires adoption of a complete framework to use the state machine.
  3. No compiler support – relies upon new C++ language features not supported by the compiler.
  4. High learning curve – some require quite a lot of effort in this regard. 
  5. Difficult syntax – non-intuitive state machine expression with hard to diagnose compiler errors. 
  6. External libraries – relies upon external libraries that add size or aren't supported on the platform.
  7. Too many features – full UML compliance isn't required and therefore exceeds the basic needs of the problem at hand. 
  8. No event data – can't send unique event data to a state function.
  9. Central event handler – a single event handling function and a switch statement handles events for each class.
  10. No type safety – requires manual typecasting the event data based on an enumeration.
  11. Limited transition rules – no support for event ignored and can't happen event transitions. 
  12. No thread-safety – the code is not thread-safe. 

Don't get me wrong, some implementations are quite impressive and suitable for many different projects. Every design has certain tradeoffs and this one is no different. Only you can decide if this one meets your needs or not. I'll try to get you bootstrapped as quickly as possible through this article and sample code. This state machine has the following features:

  1. Compact – the StateMachine class is not a template – only 448 bytes of code on Windows. Templates are used sparingly. 
  2. Transition tables – transition tables precisely control state transition behavior. 
  3. Events – every event is a simple public instance member function with any argument types. 
  4. State action – every state action is a separate instance member function with a single, unique event data argument if desired.
  5. Guards/entry/exit actions – optionally a state machine can use guard conditions and separate entry/exit action functions for each state.
  6. State machine inheritance – supports inheriting states from a base state machine class. 
  7. State function inheritance – supports overriding a state function within a derived class. 
  8. Macros – optional multiline macro support simplifies usage by automating the code "machinery".
  9. Type safe – compile time checks catch mistakes early. Runtime checks for the other cases. 
  10. Thread-safe – adding software locks to make the code thread-safe is easy.

This state machine design is not trying to achieve a full UML feature set. It is also not a Hierarchical State Machine (HSM). Instead, its goal is to be relatively compact, portable, and easy to use traditional Finite State Machine (FSM) with just enough unique features to solve many different problems. 

The article is not a tutorial on the best design decomposition practices for software state machines. I'll be focusing on state machine code and simple examples with just enough complexity to facilitate understanding the features and usage. 

Background 

A common design technique in the repertoire of most programmers is the venerable finite state machine (FSM). Designers use this programming construct to break complex problems into manageable states and state transitions. There are innumerable ways to implement a state machine. 

A switch statement provides one of the easiest to implement and most common version of a state machine. Here, each case within the switch statement becomes a state, implemented something like:

Hide   Copy Code

switch (currentState) {

   case ST_IDLE:

       // do something in the idle state

       break;

    case ST_STOP:

       // do something in the stop state

       break;

    // etc...

}

This method is certainly appropriate for solving many different design problems. When employed on an event driven, multithreaded project, however, state machines of this form can be quite limiting.

The first problem revolves around controlling what state transitions are valid and which ones are invalid. There is no way to enforce the state transition rules. Any transition is allowed at any time, which is not particularly desirable. For most designs, only a few transition patterns are valid. Ideally, the software design should enforce these predefined state sequences and prevent the unwanted transitions. Another problem arises when trying to send data to a specific state. Since the entire state machine is located within a single function, sending additional data to any given state proves difficult. And lastly these designs are rarely suitable for use in a multithreaded system. The designer must ensure the state machine is called from a single thread of control.

Why use a state machine?

Implementing code using a state machine is an extremely handy design technique for solving complex engineering problems. State machines break down the design into a series of steps, or what are called states in state-machine lingo. Each state performs some narrowly defined task. Events, on the other hand, are the stimuli, which cause the state machine to move, or transition, between states. 

To take a simple example, which I will use throughout this article, let's say we are designing motor-control software. We want to start and stop the motor, as well as change the motor's speed. Simple enough. The motor control events to be exposed to the client software will be as follows:

  1. Set Speed – sets the motor going at a specific speed.
  2. Halt – stops the motor.

These events provide the ability to start the motor at whatever speed desired, which also implies changing the speed of an already moving motor. Or we can stop the motor altogether. To the motor-control class, these two events, or functions, are considered external events. To a client using our code, however, these are just plain functions within a class. 

These events are not state machine states. The steps required to handle these two events are different. In this case the states are:

  1. Idle — the motor is not spinning but is at rest.
  • Do nothing.
  1. Start — starts the motor from a dead stop.
  • Turn on motor power.
  • Set motor speed.
  1. Change Speed — adjust the speed of an already moving motor.
  • Change motor speed.
  1. Stop — stop a moving motor.
  • Turn off motor power.
  • Go to the Idle state.

As can be seen, breaking the motor control into discreet states, as opposed to having one monolithic function, we can more easily manage the rules of how to operate the motor.

Every state machine has the concept of a "current state." This is the state the state machine currently occupies. At any given moment in time, the state machine can be in only a single state. Every instance of a particular state machine class can set the initial state during construction. That initial state, however, does not execute during object creation. Only an event sent to the state machine causes a state function to execute.

To graphically illustrate the states and events, we use a state diagram. Figure 1 below shows the state transitions for the motor control class. A box denotes a state and a connecting arrow indicates the event transitions. Arrows with the event name listed are external events, whereas unadorned lines are considered internal events. (I cover the differences between internal and external events later in the article.)

https://www.codeproject.com/KB/cpp/1087619/Motor.png
 
Figure 1: Motor state diagram 

As you can see, when an event comes in the state transition that occurs depends on state machine's current state. When a SetSpeed event comes in, for instance, and the motor is in the Idle state, it transitions to the Start state. However, that same SetSpeed event generated while the current state is Start transitions the motor to the ChangeSpeed state. You can also see that not all state transitions are valid. For instance, the motor can't transition from ChangeSpeed to Idle without first going through the Stop state.

In short, using a state machine captures and enforces complex interactions, which might otherwise be difficult to convey and implement.

Internal and external events

As I mentioned earlier, an event is the stimulus that causes a state machine to transition between states. For instance, a button press could be an event. Events can be broken out into two categories: external and internal. The external event, at its most basic level, is a function call into a state-machine object. These functions are public and are called from the outside or from code external to the state-machine object. Any thread or task within a system can generate an external event. If the external event function call causes a state transition to occur, the state will execute synchronously within the caller's thread of control. An internal event, on the other hand, is self-generated by the state machine itself during state execution.

A typical scenario consists of an external event being generated, which, again, boils down to a function call into the class's public interface. Based upon the event being generated and the state machine's current state, a lookup is performed to determine if a transition is required. If so, the state machine transitions to the new state and the code for that state executes. At the end of the state function, a check is performed to determine whether an internal event was generated. If so, another transition is performed and the new state gets a chance to execute. This process continues until the state machine is no longer generating internal events, at which time the original external event function call returns. The external event and all internal events, if any, execute within the caller's thread of control.

Once the external event starts the state machine executing, it cannot be interrupted by another external event until the external event and all internal events have completed execution if locks are used. This run to completion model provides a multithread-safe environment for the state transitions. Semaphores or mutexes can be used in the state machine engine to block other threads that might be trying to be simultaneously access the same object. See source code function ExternalEvent() comments for where the locks go. 

Event data

When an event is generated, it can optionally attach event data to be used by the state function during execution. Once the state has completed execution, the event data is considered used up and must be deleted. Therefore, any event data sent to a state machine must be created on the heap, via operator new, so that the state machine can delete it once used. In addition, for our particular implementation the event data must inherit from the EventData base class. This gives the state machine engine a common base class for which to delete all event data.

Hide   Copy Code

class EventData

{

public:

    virtual ~EventData() {}

};

The state machine implementation now has a build option that removes the requirement to create external event data on the heap. See the External event no heap data section for details. 

State transitions

When an external event is generated, a lookup is performed to determine the state transition course of action. There are three possible outcomes to an event: new state, event ignored, or cannot happen. A new state causes a transition to a new state where it is allowed to execute. Transitions to the existing state are also possible, which means the current state is re-executed. For an ignored event, no state executes. However, the event data, if any, is deleted. The last possibility, cannot happen, is reserved for situations where the event is not valid given the current state of the state machine. If this occurs, the software faults.

In this implementation, internal events are not required to perform a validating transition lookup. The state transition is assumed to be valid. You could check for both valid internal and external event transitions, but in practice, this just takes more storage space and generates busywork for very little benefit. The real need for validating transitions lies in the asynchronous, external events where a client can cause an event to occur at an inappropriate time. Once the state machine is executing, it cannot be interrupted. It is under the control of the class's private implementation, thereby making transition checks unnecessary. This gives the designer the freedom to change states, via internal events, without the burden of updating transition tables.

StateMachine class

Two base classes are necessary when creating your own state machine: StateMachine and EventData. A class inherits from StateMachine to obtain the necessary mechanisms to support state transitions and event handling. The StateMachine header also contains various preprocessor multiline macros to ease implementation of the state machine (explained later in the article). To send unique data to the state functions, the structure must inherit from the EventData base class.

The state machine source code is contained within the StateMachine.cpp and StateMachine.h files (see attached StateMachine.zip). The code below shows the class declaration.

Hide   Shrink https://www.codeproject.com/images/arrow-up-16.png   Copy Code

class StateMachine

{

public:

    enum { EVENT_IGNORED = 0xFE, CANNOT_HAPPEN };

 

    StateMachine(BYTE maxStates, BYTE initialState = 0);

    virtual ~StateMachine() {}

 

    BYTE GetCurrentState() { return m_currentState; }

   

protected:

    void ExternalEvent(BYTE newState, const EventData* pData = NULL);

    void InternalEvent(BYTE newState, const EventData* pData = NULL);

   

private:

    const BYTE MAX_STATES;

    BYTE m_currentState;

    BYTE m_newState;

    BOOL m_eventGenerated;

    const EventData* m_pEventData;

 

    virtual const<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值