【问题标题】:Type_index use, destruction, and synchronization in the state machine implementation状态机实现中type_index的使用、销毁和同步
【发布时间】:2013-11-19 14:10:20
【问题描述】:

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

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

还有几个问题(按照 C++ 标准,我很环保)。他们在这里:

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

下面是模板本身和主要的 Turnstile 类,从the Wikipedia article 严格实现。如果需要,其他六个具有特定类型和状态逻辑的实现文件位于my GitHub FSM repository

下面的 FSM 模板采用对消息做出反应的逻辑向量,向量的头部定义初始状态。它将每个消息参与者包装在一个状态对象中,将状态数据附加到它上面。当 FSM 模板运行一个状态时,它还可以检查返回的“下一个”状态键是否有效(未来,此处省略)。

这是模板:

#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; };

    }
  }
}

非常感谢您的回答!

[更新 131120] 提及FSM.h 并不意味着是具有“公共接口”的库,这可能会有所帮助。更重要的是,它是让 FSM 作者前进的样板代码。实际的用户 FSM 接口由该 FSM 实现定义 - 在附加的示例中,它位于 Turnstile.h 中。因此,FSM.h 模板非常简短,旨在供 FSM 作者阅读和理解。

我还希望他们(我自己现在)理解模板中记录的缺陷——例如在 State()()method 中向任何 FSM 发送消息将死锁的事实。这可能是由一系列调用引起的。 Thanks to Florian Philipp comment on Google+ 指出这一点。

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

【问题讨论】:

  • 你有什么理由不使用Meta State Machine
  • 目标代码库上还不允许使用第三方库。此外,上述解决方案旨在最大限度地减少那里的重构。
  • @maxim-yegorushkin(以上评论提及)

标签: c++ templates c++11


【解决方案1】:

附带说明,如果您在编译时了解您的状态机,那么使用像 Maxim 提到的 Boost.MSM 可能会更容易。

现在回答你的问题。

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

  2. shared_ptr 将正确清理,但是,请考虑在此处避免它。看起来真的没有共享所有权。对于内容,决定是 FSM 还是用户代码拥有它,并且对 FSM::content 使用 unique_ptr 或 raw ptr/reference,其余的可以是 raw ptrs/refs。我也会让 React 归 FSM 所有。 typedef Flux 为 vector> 并将所有权移动到状态对象中。

  3. 这里的销毁顺序应该没问题。

  4. 我不知道有一种简单的方法可以测试销毁顺序的正确性,但是您所有的析构函数都很简单,因此顺序并不重要。

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

【讨论】:

  • 1.知道了。 2. 明白了。我在将 Flux 制作成 unique_ptr 的向量时遇到了一些麻烦——在某个地方涉及到我还没有看到的副本。 3. 谢谢。 4. 谢谢。 5. 明白了。我一直在思考内容。其他人在the related Google+ post 也指出了这一点。
  • @VladDidenko 如果复制 c'tor/assignment 被击中,您可能需要使用 std::move 包装您传递的对象。
猜你喜欢
  • 1970-01-01
  • 2021-04-05
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-19
  • 1970-01-01
相关资源
最近更新 更多