Объектно-ориентированный конечный автомат C

#c #state-machine

#c #Конечный автомат

Вопрос:

У меня есть программный конечный автомат, который работает с использованием вызовов функций, управляемых событиями. А именно, у меня есть дескриптор конечного автомата для структуры, которая содержит указатель на функцию, представляющий текущее состояние:

 typedef struct pHandle_t pHandle_t;
typedef void(*pState_f)(pHandle_t *pHandle, pEvent_t pEvent);
struct pHandle_t
{
    pState_f curState;
    void *contextPtr;       // Is this needed?
};
 

Затем каждое состояние представляется функцией, которая принимает дескриптор для себя и событие в качестве входных данных:

 static void SM_Init(pHandle_t *pHandle, pEvent_t pEvent);
 

Внутри каждой функции есть переключатель / регистр на pEvent, который обычно производит некоторое действие, а также изменяет указатель функции curState для изменения на функцию, представляющую состояние. Весь этот код работает очень хорошо при использовании глобальных переменных для определения того, когда выполняются определенные изменения состояния. (Очевидно, что такой подход не будет хорошо работать с переменными, созданными с помощью функции, чтобы попытаться узнать, когда остановиться).

Однако в интересах сокращения глобальных переменных и функций я хотел бы реализовать более объектно-ориентированный подход, чтобы мои глобальные переменные и память команд исчезли, как только конечный автомат достиг полного состояния. Я знаю, что есть несколько примеров того, как сделать код объектно-ориентированным в обычном C, но здесь есть один ключ в механизмах: внешние модули должны иметь возможность отправлять события на этот конечный автомат, не имея указателя на дескриптор конечного автомата. Как я могу это сделать, не требуя, чтобы общий объект конечного автомата был объявлен как глобальный (что действительно противоречит первоначальному намерению попытаться освободить все это пространство, как только я с ним закончу)?

Заранее прошу прощения, если некоторые из этих формулировок сбивают с толку. Я считаю, что это очень полезная тема, поэтому я перефразирую в соответствии с просьбой, но, пожалуйста, прокомментируйте, прежде чем проголосовать против меня… Кроме того, прежде чем кто-либо спросит, вся существующая кодовая база состоит из C, и предложение C остается без внимания, несмотря на все мои усилия.

Комментарии:

1. «Внешние модули должны иметь возможность отправлять события на этот конечный автомат, не имея указателя на дескриптор конечного автомата». … что у них есть? Если в модулях нет средств выбора между различными конечными автоматами, у вас может быть только одноэлементный.

2. Ограничение кажется произвольным. Почему функция не может присвоить внешним модулям указатель create_state_machine ?

3. @Potatoswatter : В этом случае вы делаете очень хорошее замечание, для моего конкретного случая использования это будет только одноэлементный, но я хотел бы подумать о том, как ответить как на одноэлементный, так и не одноэлементный. Обновит вопрос.

4. @luserdroog: Другие модули имеют существующий API, который было бы чрезвычайно сложно модифицировать (одна из проблем старой, большой базы кода). Самое большее, что я могу сделать, это прикрепить указатель функции обратного вызова к объекту, который я передаю в функции других модулей, чтобы он в конечном итоге вернулся ко мне, и я мог принимать решения о состоянии на основе того, что возвращается. Присоединение дескриптора конечного автомата к этой структуре создает дополнительные накладные расходы на объект, который перемещается между модулями.

5. Возможно, вы захотите ознакомиться с открытым исходным кодом QP / C state machine framework, который использует именно объектно-ориентированные иерархические конечные автоматы (UML statecharts). Помимо реализации конечного автомата, фреймворк также предоставляет контекст для выполнения конечных автоматов. Просто погуглите QP / C.

Ответ №1:

Это, вероятно, ужасная идея, но как таковая, она может помочь убедить ваших коллег разрешить вам хранить один маленький, жалкий указатель там, где он вам нужен.

Если все, что вам нужно для идентификации экземпляров объектов конечного автомата, — это указатель на функцию обратного вызова, то для запуска нескольких машин вам понадобятся дубликаты всех функций, чтобы разные указатели могли быть функционально идентичными, но сравниваться по-разному при приведении к char * .

Таким образом, для двух машин вам потребуется примерно удвоить размер кода. Три машины: тройная. И т.д.

<Содрогание>

Комментарии:

1. Это именно тот тип обоснования, который я искал, потому что чем больше я думаю об этом, тем больше понимаю, что указатель дескриптора всегда будет в конечном итоге наименее затратным способом сделать это, не назначая некоторую фиксированную часть карты памяти только для этого объекта.