状态机实现中的Type_index使用、销毁和同步

Type_index use, destruction, and synchronization in the state machine implementation

本文关键字:同步 使用 index 实现 Type 状态机      更新时间:2023-10-16

在一个正在进行的项目中,需要实现多个有限状态机。一个通用层会很好,这样可以开发类似的制衡机制。状态机的要求足够多样化,所以我希望在FSM模板之外确定数据类型,作为用户的责任。此外,我希望持久数据完全由FSM实现作者控制。最后,线程安全是模板中状态转换的必要条件。

这导致了我认为是一个精益状态转换代码。其思想是FSM模板应该由FSM实现者专门化封装到可用的外部类中。该包装器类将是一个完全特定的FSM实现。FSM模板使用在其外部定义的数据类型和逻辑,但在FSM的最终包装器实现之外,这些状态和数据不应可见。

还有一些问题(按照C++标准,我是非常环保的)。它们在这里:

  1. 在这种情况下,使用type_index作为映射键是一种奇怪的做法,还是符合type_index的设计意图?它足够独特吗
  2. shared_ptr的"魔力"会自动正确地销毁包装类中实例化的所有数据,这是对的吗
  3. 此模板和实现中是否存在拆卸订单问题
  4. 有没有一种方法可以测试正确的破坏和它的顺序
  5. 是否有一种方法(或合理的需求)来测试FSM::operator()方法中的状态转换是否真正同步

下面是模板本身和主要的Turnstile类,从维基百科的文章中艰难地实现了这一点。如果需要的话,其他六个具有特定类型和状态逻辑的实现文件在我的GitHub FSM存储库中。

下面的FSM模板采用对消息作出反应的逻辑向量,向量的头部定义初始状态。它将每个消息参与者封装在一个状态对象中,并通过管道将状态数据附加到其中。当FSM模板运行一个状态时,它还可以进行清理检查,以确保返回的"下一个"状态密钥是有效的(future,此处为简洁起见省略)。

这是模板:

#pragma once
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <typeindex>
#include <mutex>
namespace tools {
  template <typename Message, typename Content>
  class FSM {
  public:

    class React {
    public:
      virtual std::type_index operator()( const Message & msg, std::shared_ptr<Content> ) {
        return std::type_index(typeid(this));
      };
    };

    typedef std::vector<std::shared_ptr<React>> Flux;

    FSM( Flux & flux, std::shared_ptr<Content> ctnt )
    : current( std::type_index( typeid( *( flux.at(0) ) ) ) )
    , content( ctnt )
    {
      for ( auto rct: flux ) {
        states[ std::type_index( typeid( *rct ) ) ] = State( rct, content );
      };
    };
    void operator()( const Message & msg ) {
      std::lock_guard<std::mutex> lock( fsm_mutex );
      current = states.at( current )( msg );
    };

  private:

    class State {
    public:
      State( std::shared_ptr<React> rct, std::shared_ptr<Content> ctnt )
      : content( ctnt ), react( rct ) {};
      State() : content( nullptr ), react( nullptr ) {};
      std::type_index operator()( const Message & message ) {
        return (*react)( message, content );
      }
    private:
      std::shared_ptr<Content> content;
      std::shared_ptr<React> react;
    };

    std::mutex fsm_mutex;
    std::shared_ptr<Content> content;
    std::type_index current;
    std::map<std::type_index, State> states;
  };
}

实现类标题:

#pragma once
#include "TurnstileTypes.h"
namespace tools {
  namespace test {
    namespace fsm {
      // Implementation of the Turnstile FSM example from http://en.wikipedia.org/wiki/Finite_state_machine#Example:_a_turnstile
      // Added RESET event to better test state.
      class Turnstile {
      public:
        Turnstile(void);
        void operator()( const TurnstileEvent & msg );
        unsigned long Pushed();
        unsigned long Paid();
        std::string Now();
      private:
        TurnstileFSM::Flux flux;
        std::unique_ptr<TurnstileFSM> fsm;
        std::shared_ptr<TurnstileData> stats;
      };
    }
  }
}

实现类主体:

#include "TurnstileTypes.h"
#include "TurnstileLocked.h"
#include "TurnstileUnlocked.h"
#include "Turnstile.h"
namespace tools {
  namespace test {
    namespace fsm {
      Turnstile::Turnstile(void)
      : stats( std::make_shared<TurnstileData>() )
      {
        flux.push_back( std::make_shared<Locked>() );
        flux.push_back( std::make_shared<Unlocked>() );
        fsm = std::unique_ptr<TurnstileFSM>( new TurnstileFSM( flux, stats ) );
      };
      void Turnstile::operator()( const TurnstileEvent & msg ) {
        (*fsm)( msg );
      };

      unsigned long Turnstile::Pushed() { return stats->pushes; };
      unsigned long Turnstile::Paid()   { return stats->coins;  };
      std::string Turnstile::Now()      { return stats->state_name; };
    }
  }
}

非常感谢你的回答!

[update 131120]值得一提的是,FSM.h并不意味着是一个具有"公共接口"的库。更重要的是,这是一个让FSM作者继续工作的样板代码。实际用户FSM接口由FSM实现定义——在所附示例中,它位于Turnstile.h中。因此,FSM.h模板非常简短,旨在供FSM作者阅读和理解。

我还希望他们(目前我自己)理解模板的缺陷,比如在State()()方法中向任何FSM发送消息都会被死锁。这可能是由一连串的呼叫引起的。感谢Florian Philipp在Google+上的评论指出了这一点。

还有一个目的是在FSM中对MessageContent类型了解最少,这样FSM作者在实现时就有了最大的灵活性,包括通过访问器之类的东西从内容中获取信息。这种灵活性伴随着不更改内容的责任。

顺便说一句,如果你在编译时了解你的状态机,那么像Maxim提到的那样使用Boost.MSM可能会更容易。

现在回答您的问题。

  1. 我相信type_index是为与map和undered_map一起使用而设计的,所以您可以按设计使用它。

  2. shared_ptr将正确地进行清理,但是,请考虑在此处避免它。看起来真的没有共同所有权。对于内容,决定FSM或用户代码是否拥有它,并对FSM::content使用unique_ptr或原始ptr/reference,其余可以是原始ptr/refs。我也会让React归FSM所有。typedef通量为向量<unique_ptr<React>gt;并将所有权移动到状态对象中。

  3. 销毁命令在这里应该没问题。

  4. 我不知道有什么简单的方法可以测试销毁顺序的正确性,但你所有的销毁程序都很琐碎,所以顺序并不重要。

  5. 您的公共接口由一个通过互斥对象序列化的方法组成——实际上不需要测试它是否会同步。如果用户触摸React子类化对象,也可能会导致问题,但是,如果您将它们移动(而不是共享它们)到FSM中,这就不是问题。此外,请不要从FSM内外读取/更新内容,这可能会导致同步问题。