在 C++ 中实现状态机

发布于 2024-09-06 12:01:29 字数 86 浏览 8 评论 0原文

我是 C++ 新手。

如何用 C++ 实现状态机?

我只收到消息,应该知道下一个状态。

我需要使用的正确结构是什么?

I'm new to C++.

How can I implement a State Machine in C++?

I'm getting only the messages and should know the next state.

What is the proper structure I need to use?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

帅哥哥的热头脑 2024-09-13 12:01:30

对于简单的状态机,您可以在循环内使用 switch 语句,例如

for (;;)
{
    switch (state)
    {

    case STATE_1:
        // do stuff
        // maybe change state
        break;

    case STATE_2:
        // do stuff
        // maybe change state
        break;

    case STATE_3:
        // do stuff
        // maybe change state
        break;

    // ...

    }
}

For simple state machines you can just use a switch statement inside a loop, e.g.

for (;;)
{
    switch (state)
    {

    case STATE_1:
        // do stuff
        // maybe change state
        break;

    case STATE_2:
        // do stuff
        // maybe change state
        break;

    case STATE_3:
        // do stuff
        // maybe change state
        break;

    // ...

    }
}
征棹 2024-09-13 12:01:30
typedef std::pair<State,Message> StateMessagePair;
typedef std::map<StateMessagePair,State> StateDiagram;
StateDiagram sd;
// add logic to diagram
...
State currentState = getInitialState();
...
// process message
Message m = getMessage();
StateDiagram::iterator it=sd.find(std::make_pair(currentState,m)));
if (it==sd.end()) exit("incorrect message");
currentState = it->second;

编辑:
构建状态图是这样完成的(示例是可乐自动售货机):

StateDiagram.insert(std::make_pair(State::Idle           ,Message::MakeChoice   ),State::WaitingForMoney);
StateDiagram.insert(std::make_pair(State::WaitingForMoney,Message::Cancel       ),State::Idle);
StateDiagram.insert(std::make_pair(State::WaitingForMoney,Message::MoneyEntered ),State::FindCan);
StateDiagram.insert(std::make_pair(State::FindCan        ,Message::CanSentToUser),State::Idle);

实现默认操作,其中键只是状态,如下所示:

typedef std::map<State,State> StateDiagramForDefaults;

可以使用第二个映射来 逻辑可以在 StateDiagramForDefaults 中执行查找。

如果需要将操作添加到状态图,则映射的值应该是由操作和新状态组成的对,如下所示:

typedef std::pair<State,Message> StateMessagePair;
typedef std::pair<State,IAction *> StateActionPair;
typedef std::map<StateMessagePair,StateActionPair> StateDiagram;

构建图的逻辑应该“新”类的实例实现 IAction,并将其放入 StateDiagram 中。

然后,执行逻辑仅通过虚拟方法(例如execute() 或() 运算符)执行IAction 实现。

typedef std::pair<State,Message> StateMessagePair;
typedef std::map<StateMessagePair,State> StateDiagram;
StateDiagram sd;
// add logic to diagram
...
State currentState = getInitialState();
...
// process message
Message m = getMessage();
StateDiagram::iterator it=sd.find(std::make_pair(currentState,m)));
if (it==sd.end()) exit("incorrect message");
currentState = it->second;

EDIT:
Building up the state diagram is done like this (example is for a cola-vending machine):

StateDiagram.insert(std::make_pair(State::Idle           ,Message::MakeChoice   ),State::WaitingForMoney);
StateDiagram.insert(std::make_pair(State::WaitingForMoney,Message::Cancel       ),State::Idle);
StateDiagram.insert(std::make_pair(State::WaitingForMoney,Message::MoneyEntered ),State::FindCan);
StateDiagram.insert(std::make_pair(State::FindCan        ,Message::CanSentToUser),State::Idle);

Default actions can be implemented using a second map, where the key is only the State, like this:

typedef std::map<State,State> StateDiagramForDefaults;

Instead of printing "incorrect message", the logic can perform a lookup in the StateDiagramForDefaults.

If actions needs to be added to the state diagram, the value of the map should be a pair consisting of an action, and a new state, like this:

typedef std::pair<State,Message> StateMessagePair;
typedef std::pair<State,IAction *> StateActionPair;
typedef std::map<StateMessagePair,StateActionPair> StateDiagram;

The logic that builds up the diagram should then "new" an instance of a class that implements IAction, and put that in the StateDiagram.

The executing logic then just executes the IAction implementation via a virtual method (e.g. execute() or the ()-operator).

后eg是否自 2024-09-13 12:01:30

标准状态机实现技术是:

  1. 嵌套 switch 语句(一些
    以前的帖子显示了这方面的例子
    技术)
  2. 状态表
  3. GoF 状态设计模式
  4. 上述的组合

如果您不熟悉状态机和 C 或 C++ 实现,我会推荐我的 Dr.Dobbs 文章“回归基础”,可在 http://www.ddj.com/184401737 (您需要点击顶部的打印链接才能方便地阅读文本。)

没有一种标准技术适合分层状态机(例如 UML 状态图)。如果您对现代 UML 状态机感兴趣,我会推荐我的由 3 部分组成的 Embedded.com 文章“UML 状态机速成课程”(http://www.embedded.com/design/testissue/215801043)。

The standard state machine implementation techniques are:

  1. the nested switch statement (some
    previous posts show examples of this
    technique)
  2. state-table
  3. the GoF State design pattern
  4. combination of the above

If you are new to state machines and implementation in C or C++, I would recommend my Dr.Dobbs article "Back to Basics" available at http://www.ddj.com/184401737 (you need to click on the Print link at the top to conveniently read the text.)

None of the standard techniques is suitable for the hierarchical state machines (such as UML statecharts). If you are interested in the modern UML state machines, I'd recommend my 3-part Embedded.com article "A crash course in UML state machines" (http://www.embedded.com/design/testissue/215801043).

生生漫 2024-09-13 12:01:30

除非您为了实现状态机而实现状态机,否则我强烈建议您使用状态机生成器。 Ragel 是一种非常好的、强大的解决方案。

https://github.com/bnoordhuis/ragel

Unless you are implementing a state machine for the sake of implementing one, I would highly recommend that you use a state machine generator instead. Ragel is one very good and robust solution.

https://github.com/bnoordhuis/ragel

半岛未凉 2024-09-13 12:01:30

一种方法是使用这样的类(前面有粗略的示例代码):

class State
{
  //pass a new Message into the current State
  //current State does (state-specific) processing of
  //the message, and returns a pointer to the new State
  //if there's a state change
  virtual State* getNewState(const Message&) = 0;
};

class ExampleState
{
  virtual State* getNewState(const Message& message)
  {
    switch (message.type)
    {
      case MessageType.Stop:
        //change state to stopped
        return new StoppedState();
    }
    //no state change
    return 0;
  }
};

其中一个复杂的问题是状态是否应该是 静态享元,或者它们是否携带实例数据并因此应该被更新和删除。

One way is to use a class like this (rough example code ahead):

class State
{
  //pass a new Message into the current State
  //current State does (state-specific) processing of
  //the message, and returns a pointer to the new State
  //if there's a state change
  virtual State* getNewState(const Message&) = 0;
};

class ExampleState
{
  virtual State* getNewState(const Message& message)
  {
    switch (message.type)
    {
      case MessageType.Stop:
        //change state to stopped
        return new StoppedState();
    }
    //no state change
    return 0;
  }
};

On of the complications is whether states should be static flyweights, or whether they carry instance data and should therefore be newed and deleted.

牵你手 2024-09-13 12:01:30

天哪,事情并不像看起来那么复杂。状态机代码非常简单和简短。

编写状态机代码几乎是微不足道的。困难的部分是设计一个在所有可能的情况下都能正确运行的状态机。

但让我们假设您有正确的设计。你如何编码?

  1. 将状态存储在属性中,可能是 myState。

  2. 每当您收到消息时,都会在 myState 属性上切换以执行该状态的代码。

    每当

3 在每个状态下,打开消息以执行该状态和该消息的代码

因此您需要一个嵌套的 switch 语句

cStateMachine::HandleMessage( msg_t msg )
{
  switch( myState ) {

     case A:

        switch( msg ) {

           case M:

           // here is the code to handle message M when in state A

...

一旦您启动并运行它,添加更多状态和消息就会变得有趣且容易。

Gosh, it isn’t as complicated as it seems. State machine code is very simple and short.

Coding a state machines is just about trivial. The hard part is designing a state machine that behaves correctly in all possible cases.

But let us assume that you have the correct design. How do you code it?

  1. Store the state in an attribute, myState perhaps.

  2. Whenever you receive a message switch on the myState attribute to execute the code for that state.

3 In each state, switch on the message to execute the code for that state AND that message

So you need a nested switch statement

cStateMachine::HandleMessage( msg_t msg )
{
  switch( myState ) {

     case A:

        switch( msg ) {

           case M:

           // here is the code to handle message M when in state A

...

Once you have this up and running, it is fun and easy to add more states and messages.

匿名。 2024-09-13 12:01:30

请参阅在程序中实现有限自动机了解几种不同的方法构建有限状态机。

See Implementing Finite Automata in a Program for several different ways of building finite state machines.

小猫一只 2024-09-13 12:01:30

查看分层状态机文章。通过示例描述了将状态建模为类。

Checkout the hierarchical state machine article. Modeling of states as classes is described with an example.

或十年 2024-09-13 12:01:30

我正在使用的基于此:机器对象

它似乎经过了很好的优化,并且使用起来比 Boost 状态图

The one i am using is based on this one: Machine Objects.

It seems to be well optimized and is a hell of a lot less complex to use than Boost Statechart.

星星的軌跡 2024-09-13 12:01:30

当然,Boost 为您准备了一些东西: Boost状态图库。您还可以在那里找到一些不错的教程。

Of course, boost has something in store for you: Boost Statechart library. You can also find some nice tutorials there.

羁绊已千年 2024-09-13 12:01:30
switch(currentState) {
    state1:
         currentState = NEXT_STATE2;
         break;
    ....
}
switch(currentState) {
    state1:
         currentState = NEXT_STATE2;
         break;
    ....
}
情未る 2024-09-13 12:01:30

您可以使用 Ragel 状态机编译器: http://www.complang.org/ragel/

Ragel 从常规编译可执行有限状态机
语言。 Ragel 的目标是 C、C++ 和 ASM。 Ragel 状态机不能
只像正则表达式机器那样识别字节序列,但是
还可以在识别的任意点执行代码
常规语言。代码嵌入是使用内联运算符完成的
不要破坏常规语言语法。

核心语言由标准正则表达式运算符组成
(例如并集、串联和 Kleene 星)和动作嵌入
运营商。用户的正则表达式被编译为
确定性状态机和嵌入动作相关联
随着机器的转变。了解正式的
正则表达式与确定性有限元之间的关系
自动机是有效使用 Ragel 的关键。

Ragel 还提供了可让您控制任何非确定性的运算符
您使用以下命令创建、构建扫描器并构建状态机
状态图模型。也有可能影响某项任务的执行
通过跳转或调用从嵌入动作内部状态机
机器的其他部分,或重新处理输入。

Ragel 为宿主语言提供了一个非常灵活的接口
尝试对生成代码的方式施加最小的限制
集成到应用程序中。生成的代码没有
依赖关系。

Ragel 代码如下所示:

action dgt      { printf("DGT: %c\n", fc); }
action dec      { printf("DEC: .\n"); }
action exp      { printf("EXP: %c\n", fc); }
action exp_sign { printf("SGN: %c\n", fc); }
action number   { /*NUMBER*/ }

number = (
    [0-9]+ $dgt ( '.' @dec [0-9]+ $dgt )?
    ( [eE] ( [+\-] $exp_sign )? [0-9]+ $exp )?
) %number;

main := ( number '\n' )*;

.. 并编译为:

st0:
    if ( ++p == pe )
        goto out0;
    if ( 48 <= (*p) && (*p) <= 57 )
        goto tr0;
    goto st_err;
tr0:
    { printf("DGT: %c\n", (*p)); }
st1:
    if ( ++p == pe )
        goto out1;
    switch ( (*p) ) {
        case 10: goto tr5;
        case 46: goto tr7;
        case 69: goto st4;
        case 101: goto st4;
    }
    if ( 48 <= (*p) && (*p) <= 57 )
        goto tr0;
    goto st_err;

You can use the Ragel state machine compiler: http://www.complang.org/ragel/

Ragel compiles executable finite state machines from regular
languages. Ragel targets C, C++ and ASM. Ragel state machines can not
only recognize byte sequences as regular expression machines do, but
can also execute code at arbitrary points in the recognition of a
regular language. Code embedding is done using inline operators that
do not disrupt the regular language syntax.

The core language consists of standard regular expression operators
(such as union, concatenation and Kleene star) and action embedding
operators. The user’s regular expressions are compiled to a
deterministic state machine and the embedded actions are associated
with the transitions of the machine. Understanding the formal
relationship between regular expressions and deterministic finite
automata is key to using Ragel effectively.

Ragel also provides operators that let you control any non-determinism
that you create, construct scanners, and build state machines using a
statechart model. It is also possible to influence the execution of a
state machine from inside an embedded action by jumping or calling to
other parts of the machine, or reprocessing input.

Ragel provides a very flexible interface to the host language that
attempts to place minimal restrictions on how the generated code is
integrated into the application. The generated code has no
dependencies.

Ragel code looks like:

action dgt      { printf("DGT: %c\n", fc); }
action dec      { printf("DEC: .\n"); }
action exp      { printf("EXP: %c\n", fc); }
action exp_sign { printf("SGN: %c\n", fc); }
action number   { /*NUMBER*/ }

number = (
    [0-9]+ $dgt ( '.' @dec [0-9]+ $dgt )?
    ( [eE] ( [+\-] $exp_sign )? [0-9]+ $exp )?
) %number;

main := ( number '\n' )*;

.. and it compiles to:

st0:
    if ( ++p == pe )
        goto out0;
    if ( 48 <= (*p) && (*p) <= 57 )
        goto tr0;
    goto st_err;
tr0:
    { printf("DGT: %c\n", (*p)); }
st1:
    if ( ++p == pe )
        goto out1;
    switch ( (*p) ) {
        case 10: goto tr5;
        case 46: goto tr7;
        case 69: goto st4;
        case 101: goto st4;
    }
    if ( 48 <= (*p) && (*p) <= 57 )
        goto tr0;
    goto st_err;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文