пятница, 24 апреля 2015 г.

[prog.c++] Попалась на глаза любопытная заметка про Boost.Coroutine vs FiniteStateMachine

Вот она: Boost coroutines instead of state machines? Maybe... Очень интересные выводы в конце этой статьи сделаны. Имхо, желающим использовать короутины в продакшене имеет смысл ознакомиться и со статьей, и с выводами.

Я же, в силу своего темного прошлого, все-таки остаюсь приверженцем подхода на основе конечных автоматов. Да, кода приходится писать чуть больше. Зато потом нужно меньше голову ломать.

Ну и еще об одной вещи касательно конечных автоматов. В статье показан, на мой взгляд, самый корявый способ их описания: через enum и один большой switch. Между тем, в C++ на указателях можно писать более понятные и удобные в сопровождении конечные автоматы, например:

class state_machine_parser
{
private:
   char m_message[255];
   uint8_t m_message_length = 0;
   uint8_t m_message_offset = 0;

   void (state_machine_parser::*m_stage)( char ) =
         &state_machine_parser::message_header;

public:
   void process(char c)
   {
      (this->*m_stage)( c );
   }

   bool complete() const
   {
      return m_stage == &state_machine_parser::message_complete;
   }

private :
   void message_header( char c )
   {
      m_message_length = c;
      if( m_message_length == 0 )
      {
         m_stage = &state_machine_parser::message_complete;
      }
      else
      {
         m_message_offset = 0;
         m_stage = &state_machine_parser::message_body;
      }
   }

   void message_body( char c )
   {
      m_message[m_message_offset++] = c;
      if (m_message_offset == m_message_length)
      {
          // do something with the message
          m_stage = &state_machine_parser::message_header;
      }
   }

   void message_complete( char c )
   {}
};

Кстати говоря, на основе указателей можно строить и более сложные автоматы, в которых нужно иметь разные обработчики для разных типов входных воздействий. Например, у вас могут быть входные воздействия вроде SPACE, PLUS, MINUS, DIGIT, POINT, EXPONENT. И состояния EMPTY, SIGN, INT_PART, WAIT_FRACTION, FRACT_PART. Ну и, соответственно, разные типы реакций на сочетания текущего состояния и входного воздействия. Например: (EMPTY+SPACE) -- ничего не делаем, остаемся в состоянии EMPTY. (EMPTY+PLUS) или (EMPTY+MINUS) -- обрабатываем знак и переходим в SIGN. (EMPTY+DIGIT) -- обрабатываем первую цифру целой части числа и переходим в INT_PART...

Все это дело можно оформить в виде матрицы, где строками будут состояния, а столбцами -- входные воздействия. Элементами матрицы -- указатели на функции с соответствующей реакцией. Причем, для развесистых деревьев состояний не обязательно формировать матрицу переходов в С++коде вручную. Зачастую возможно описать несколько вспомогательных структур или функций, которые получат на вход последовательность триплетов (state, input, action), а на выходе дадут нужного размера матрицу.

Так же не могу не отметить, что на мой взгляд, удобство конечных автоматов начнет сказываться, если в каждом состоянии у вас будет несколько возможных входных воздействий. Но об этом уже говорилось несколько ранее.

Отправить комментарий