状态机实现

时间:2010-02-01 21:57:41

标签: c++ state-machine

我有一台状态机,如下所述。

我们可以从两种起始状态中的一种开始,但我们必须触及握手的所有4种状态。从那里,我们可以传输数据的有效载荷或接收数据的有效载荷。然后,我们回到原来的起始状态。

握手:

- > StartingState1 - > FinalState1 - > StartingState2 - > FinalState2

- > StartingState2 - > FinalState2 - > StartingState1 - > FinalState1

有效负载转移:

- > SendPayload - > SendEnd - > StartingState?

- > ReceivePayload - > ReceiveEnd - > StartingState?

以下代码代表我当前的架构。不幸的是,在每个过程结束时,我没有足够的信息来自州内,知道下一个状态应该是什么。

是否有人根据我的要求对如何改进此架构有任何建议?

谢谢,  PaulH

class MyMachine;
class Payload;

class IState
{
    MyMachine* context_;

    IState( MyMachine* context ) : context_( context) {};

    virtual void Consume( byte data );

    void ChangeState( IState* state )
    {
        context_->SetState( state );
    }
}

class FinalState1 : IState
{
    void Consume( byte data )
    {
        // Either go to StartingState1, SendPayload, or ReceivePayload.
        // How can I tell from within the context of this state where I 
        // should go?
    }
}

class StartingState1 : IState
{
    void Consume( byte data )
    {
        if ( /*some condition*/ )
        {
            ChangeState( new FinalState1( context_ ) );
        } 
    }
}

class MyMachine
{
    IState* state_;
    Payload* payload_;

    void Start1( Mode mode )
    {
        state_ = new StartingState1( this );
    }

    void Start2( Mode mode )
    {
        state_ = new StartingState2( this );
    }

    void Consume( byte data )
    {
        state_->Consume( data );
    }

    void SetPayload( const Payload* payload )
    {
        payload_ = payload;
    }

    const Payload* GetPayload()
    {
        return payload_;
    }

    void SetState( State* state )
    {
        delete state_;
        state_ = state;
    }
}

// get a byte of data from some source
byte GetData();

void main()
{
    MyMachine machine;
    Payload payload;
    machine.SetPayload( payload );
    machine.Start1( Mode::SendPayload );

    // could also call:
    // machine.Start1( Mode::ReceivePayload );
    // machine.Start2( Mode::SendPayload );
    // machine.Start2( Mode::ReceivePayload );

    for(;;)
    {
        machine.Consume( GetData() );
    }
}

5 个答案:

答案 0 :(得分:5)

你所拥有的并不能完全代表你系统的可能状态,但它很容易转换它。您需要其他状态来表示处于状态1和未处于状态2之间的区别,并且处于状态1,同时处于状态2(对于状态2而言相同)。所以你需要:

S1 S2 F1 F2 S12 F12 S21 F21
SP SE
RP RE

带过渡

S1 --> F1
F1 --> S12
S12 --> F12
F12 --> SP or F12 --> RP

S2 --> F2
F2 --> S21
S21 --> F21
F21 --> SP or F21 --> RP

SP --> SE
RP --> RE
SE --> S1 or SE --> S2
RE --> S1 or RE --> S2

关键区别在于引入了新的州S12F12S21F21。在实现方面,你几乎可以肯定只是从S2导出S12,从F2导出F12,从S1导出S21,从F2导出F21,并覆盖转换函数以进入正确的状态。

(为所有州的首字母缩略词道歉)。

答案 1 :(得分:3)

你看过boost::statechart图书馆吗?

答案 2 :(得分:2)

我建议从功能对象或函数指针的角度进行设计。

可以使用数组或std::map实现简单的状态机。使用当前状态作为索引,并检索新状态或指向状态函数的指针。

更复杂的状态机基于转换事件从一个州移动到另一个州。简单实现,这需要一个'嵌套'数组。过渡容器的容器。第一次访问为您提供状态的转换表。使用当前转换作为转换表的索引,以返回处理此转换的函数的函数指针。

可以使用不同的数据结构,这些都取决于状态机的复杂程度。

一个好主意是拥有一个表驱动的状态机。这允许引擎进行一次编码和测试。更改状态机涉及更改表中的数据。该表可能存在于可执行文件之外,这意味着可执行文件不必更改。可以使用动态库扩展此概念,从而减少更改可执行文件的需要。

这只是我的建议,我可能是错的(从Dennis Miller转述)。

答案 3 :(得分:2)

以下是使用Thomas建议的方法的示例:

#include <cassert>
#include <iostream>
#include <map>

class Machine;

typedef void (*StateFunctionPtr)(Machine& context);

// State "do" functions
void starting1(Machine& context)        {std::cout << "S1 ";}
void final1(Machine& context)           {std::cout << "F1 ";}
void starting2(Machine& context)        {std::cout << "S2 ";}
void final2(Machine& context)           {std::cout << "F2 ";}
void sendPayload(Machine& context)      {std::cout << "SP ";}
void sendEnd(Machine& context)          {std::cout << "SE ";}
void receivePayload(Machine& context)   {std::cout << "RP ";}
void receiveEnd(Machine& context)       {std::cout << "RE ";}

namespace State
{
    enum Type {start, handshake1, handshake2, handshake3,
        handshake4, xferPayload, endPayload};
};

// Aggregate of state, "mode" variables, and events.
struct StateKey
{
    // Needed for use as map key
    bool operator<(const StateKey& rhs) const
    {
        return
              (state < rhs.state)
        ||  ( (state == rhs.state) && (isReceiving < rhs.isReceiving) )
        ||  ( (state == rhs.state) && (isReceiving == rhs.isReceiving)
                && (startsAt2 < rhs.startsAt2) );
    }

    bool startsAt2;
    bool isReceiving;
    State::Type state;
};

struct StateEffect
{
    StateFunctionPtr function;  // "do" function
    State::Type newState;       // state to transition to
};

struct StatePair
{
    StateKey key;
    StateEffect effect;
};

const StatePair stateTable[] =
{
    {{0, 0, State::start},       {&starting1,       State::handshake1}},
    {{0, 0, State::handshake1},  {&final1,          State::handshake2}},
    {{0, 0, State::handshake2},  {&starting2,       State::handshake3}},
    {{0, 0, State::handshake3},  {&final2,          State::handshake4}},
    {{0, 0, State::handshake4},  {&sendPayload,     State::xferPayload}},
    {{0, 0, State::xferPayload}, {&sendEnd,         State::endPayload}},
    {{0, 0, State::endPayload},  {&starting1,       State::handshake1}},

    {{0, 1, State::start},       {&starting1,       State::handshake1}},
    {{0, 1, State::handshake1},  {&final1,          State::handshake2}},
    {{0, 1, State::handshake2},  {&starting2,       State::handshake3}},
    {{0, 1, State::handshake3},  {&final2,          State::handshake4}},
    {{0, 1, State::handshake4},  {&receivePayload,  State::xferPayload}},
    {{0, 1, State::xferPayload}, {&receiveEnd,      State::endPayload}},
    {{0, 1, State::endPayload},  {&starting1,       State::handshake1}},

    {{1, 0, State::start},       {&starting2,       State::handshake1}},
    {{1, 0, State::handshake1},  {&final2,          State::handshake2}},
    {{1, 0, State::handshake2},  {&starting1,       State::handshake3}},
    {{1, 0, State::handshake3},  {&final1,          State::handshake4}},
    {{1, 0, State::handshake4},  {&sendPayload,     State::xferPayload}},
    {{1, 0, State::xferPayload}, {&sendEnd,         State::endPayload}},
    {{1, 0, State::endPayload},  {&starting2,       State::handshake1}},

    {{1, 1, State::start},       {&starting2,       State::handshake1}},
    {{1, 1, State::handshake1},  {&final2,          State::handshake2}},
    {{1, 1, State::handshake2},  {&starting1,       State::handshake3}},
    {{1, 1, State::handshake3},  {&final1,          State::handshake4}},
    {{1, 1, State::handshake4},  {&receivePayload,  State::xferPayload}},
    {{1, 1, State::xferPayload}, {&receiveEnd,      State::endPayload}},
    {{1, 1, State::endPayload},  {&starting2,       State::handshake1}}
};


class Machine
{
public:
    Machine()
    {
        // Initialize state chart map from constant state table
        const size_t tableSize = sizeof(stateTable) / sizeof(stateTable[0]);
        for (size_t row=0; row<tableSize; ++row)
        {
            stateChart_[stateTable[row].key] = stateTable[row].effect;
        }
    }

    // If startsAt2==true, then FSM will start with starting2 handshake function
    void reset(bool startsAt2, bool isReceiving)
    {
        stateKey_.startsAt2 = startsAt2;
        stateKey_.isReceiving = isReceiving;
        stateKey_.state = State::start;
    }

    void step()
    {
        StateChart::const_iterator iter = stateChart_.find(stateKey_);
        assert(iter != stateChart_.end());
        const StateEffect& effect = iter->second;
        effect.function(*this);
        stateKey_.state = effect.newState;
    }

private:
    typedef std::map<StateKey, StateEffect> StateChart;
    StateChart stateChart_;
    StateKey stateKey_;
};

int main()
{
    Machine machine;
    machine.reset(true, false);
    for (int i=0; i<20; ++i)
    {
        machine.step();
    }
}

它在我的机器上编译和工作。您可能希望添加以下功能:

  • StateEffect中的进入/退出功能
  • StateKey中的事件“触发器”
  • 概括为模板。

为它添加足够的通用功能,它将开始类似于Boost.StateChart想要的东西。 ; - )

答案 4 :(得分:0)

您可以使用Petri网为状态机建模。这允许您定义非常简单和非常复杂的状态机。 要实现您指定的状态机/ petri网,您可以使用PTN Engine之类的引擎。

它允许您declaratively在Petri网构造函数中定义整个状态机。您可以集成自己的函数,以便在达到给定状态时调用,以及触发状态更改的函数。