实施“层级状态机” (HSM)在现代C++中

发布于 2025-01-26 10:18:39 字数 3554 浏览 3 评论 0原文

在过去的几天里,我一直在玩这个,但不确定最好的设计方法是什么 - 尤其是在顶级界面方面。我已经看过了:

https://barrgroup.com /嵌入式系统/操作/简介/介绍 - 结构 - 状态

和这篇相当古老的文章确实:

https://state-machine.com/doc/heinzmann04.pdf

,想知道如今使用现代C ++可以做些什么及其提供的功能,包括高级元编程设施,例如Boost In Boost。

对于那些不知道的人来说,“层次状态机”基本上是对“有限状态机”的重组因此,从技术上讲,每个内部“ FSM”的水平都不在过渡下关闭,因此不是真正的FSM),从而使一个更大的表现力能够建模有条件关系的关系,这些关系经常在GUIS之类的东西中出现,其中一组小部件的状态可能会影响另一个可能影响三分之一的集合,同时也改善代码重复使用。当我注意到我的旧GUI代码多么混乱,然后找到了这种模式时,我想到了一个镜头。

特别是,我的HSM的“理想”接口看起来像这样:

class MyHsm : public Hsm<MyHsm, int> {
    public:
        MyHsm() : m_data(0) {}
    private:
        int m_data;
};

class MyHsmRootState : public HsmRootState<MyHsm> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState1 : public HsmInnerState<MyHsmRootState> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState2 : public HsmDefaultInnerState<MyHsmRootState> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState11 : public HsmValidState<MyHsmState1> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState12 : public HsmDefaultValidState<MyHsmState1> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState21 : public HsmValidState<MyHsmState2> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState22 : public HsmDefaultValidState<MyHsmState2> {
    public:
        void processInput(int a_input) const {
            if(a_input == 0) {
                hsmTran<MyHsmState21>(a_hsm); // or whatever
            }
        }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

使用继承树来表示状态层次结构,并且要启动HSM,我们做的

void f() {
    MyHsm hsm;
    hsm.commence<MyHsmRootState>();
    ...
}

想法是,在调用此命令后,HSM必须过渡到最内向的默认状态(仅HSM实际上是在叶子中,即没有进一步遗传的状态,这也意味着在所有状态也在所有状态中也进一步延续了层次结构)。这是myhsmstate22。这就是我发现实施棘手的行为。

特别是,为了进行这种过渡,myhsm以某种方式必须知道, derived class myhsmstate2继承了HSMDEFAULTINNENNERSTATE&lt; myhsmrootstate&gt;来自myhsmrootstate(它知道),然后继续知道,从中, ,派生的类myhsmstate22代码>。问题是,有什么办法可以做到吗?我想出的任何内容似乎都至少非常复杂(即使不是外部代码处理器来扫描C ++代码并生成某种注释a la qt的MOC)。

我想到的替代方案是更改接口以安抚实现的约束,而是将默认设备视为类型成员,因此在Templatese中很容易访问向下点的链接:

class MyHsmRootState : public HsmRootState<MyHsm> {
    public:
        typedef class MyHsmState2 Default;

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

但是,这需要向前声明,因此实际上是有效的myhsmrootstate的周期性依赖性在myhsmstate2上,相反。也就是说,如果它简化了事情并使代码更加整洁,那就不会受伤,对吗?但是我还听说,通常引入周期性依赖性不是“好实践”。

I've been toying with this for the past couple of days but am not sure what the best way to design it is - particularly in regard to the top-level interface. I've looked at this:

https://barrgroup.com/embedded-systems/how-to/introduction-hierarchical-state-machines

and this quite old article indeed:

https://state-machine.com/doc/Heinzmann04.pdf

and am wondering what can be done nowadays with contemporary C++ and the features it offers, including advanced metaprogramming facilities such as in BOOST.

For those who don't know, a "hierarchical state machine" is basically a reorganization of the "finite state machine" so that each state becomes, in effect, its own FSM (though with the important exception that transitions are allowed to cross hierarchical levels so technically each inner "FSM" is not closed under transition and thus not a true FSM), thus allowing one greater expressive power to model relationships of conditionality that crop up often in things like GUIs where that the state of one set of widgets may affect another set which in turn may affect a third, while also improving code reuse. And when I noticed how messy my old GUI code was and then found this pattern, I thought of giving it a shot.

In particular, my "ideal" interface for the HSM would look like this:

class MyHsm : public Hsm<MyHsm, int> {
    public:
        MyHsm() : m_data(0) {}
    private:
        int m_data;
};

class MyHsmRootState : public HsmRootState<MyHsm> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState1 : public HsmInnerState<MyHsmRootState> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState2 : public HsmDefaultInnerState<MyHsmRootState> {
    public:
        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState11 : public HsmValidState<MyHsmState1> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState12 : public HsmDefaultValidState<MyHsmState1> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState21 : public HsmValidState<MyHsmState2> {
    public:
        void processInput(int a_input) const { ... }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

class MyHsmState22 : public HsmDefaultValidState<MyHsmState2> {
    public:
        void processInput(int a_input) const {
            if(a_input == 0) {
                hsmTran<MyHsmState21>(a_hsm); // or whatever
            }
        }

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

where an inheritance tree is used to represent the state hierarchy, and to initiate the HSM we do

void f() {
    MyHsm hsm;
    hsm.commence<MyHsmRootState>();
    ...
}

The idea is that, upon calling this command, the HSM must transition to the innermost default state (it is only possible for the HSM to actually be in a leaf, i.e. not further inherited, state, which then implies being also in all the states further up the hierarchy from it as well). Here that is MyHsmState22. And that's the behavior I'm finding tricky to implement.

In particular, in order to do this transition, MyHsm somehow has to know that there is a derived class MyHsmState2 that inherited HsmDefaultInnerState<MyHsmRootState> from MyHsmRootState (which it knows of), and then on to know that there is, from that, the derived class MyHsmState22. And the question is, is there some way to do that? Anything I come up with seems to be very complicated at the least (if not even an external code processor to scan the C++ code and generate some kind of annotation a la Qt's MOC).

The alternative I think of is changing the interface to appease the constraints of the implementation, and instead treat the defaults as type members, so the downward-pointing links are easily accessed in templatese:

class MyHsmRootState : public HsmRootState<MyHsm> {
    public:
        typedef class MyHsmState2 Default;

        void enter(MyHsm *a_hsm) const { ... }
        void exit(MyHsm *a_hsm) const { ... }
};

however this requires a forward declaration and thus in effect is a cyclical dependency of MyHsmRootState upon MyHsmState2 and conversely. That said, if it simplifies things and makes the code neater, it can't hurt, right? But I've also heard that generally introducing cyclical dependencies is not "good practice".

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

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

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文