Comments 17
А готовых конечных автоматов в каком-нибудь Boost совсем нету?
+1
Кстати, есть. Я когда-то смотрел — там есть вообще упоротая библиотека для супербыстрых машин состояний на темплейтной магии… Вот ссылочка с примером. Если честно, сам таким я бы пользоваться заопасался (правда, читал про неё пока мало).
+2
Я вообще C++ опасаюсь пользоваться =). Но вроде выглядит нормально. Ну, во всяком случае примерно так же, как и куча другого непонятного Boost кода.
+3
Оно нормально до тех пор, пока не начинается иерархичность машин состояний. Там дальше про иерархические машины рассказ идёт — и такая жажа начинается, что ховайся.
Вообще, меня постоянно озадачивает тот факт, что машину состояний не выносят в отдельный класс, объекты которого встраивают туда, где нужно эти состояния хранить (ну, а коллбеки байндят к вызовам класса через те же делегаты). Сам пишу игровой движок, пока застрял на уровне рендера, но когда доберусь до логики — обязательно на слои попробую разбить в этом смысле, чтобы понять в чём подвох.
Вообще, меня постоянно озадачивает тот факт, что машину состояний не выносят в отдельный класс, объекты которого встраивают туда, где нужно эти состояния хранить (ну, а коллбеки байндят к вызовам класса через те же делегаты). Сам пишу игровой движок, пока застрял на уровне рендера, но когда доберусь до логики — обязательно на слои попробую разбить в этом смысле, чтобы понять в чём подвох.
+1
Ну тут автор объясняет своё решение простотой.
У нас модель иерархическая и сложная, поэтому дерево лежит вообще отдельно. И каждое состояние — отдельный класс.
Зато бонусом идёт то, что на этой логике было сделано уже 4 проекта (в т.ч. не совсем похожие друг на друга).
Впрочем, в бэкэнде с этим проще — не надо никак отображать изменения, так что легко делать изолированный от всего конечный автомат.
У нас модель иерархическая и сложная, поэтому дерево лежит вообще отдельно. И каждое состояние — отдельный класс.
Зато бонусом идёт то, что на этой логике было сделано уже 4 проекта (в т.ч. не совсем похожие друг на друга).
Впрочем, в бэкэнде с этим проще — не надо никак отображать изменения, так что легко делать изолированный от всего конечный автомат.
+1
В данном случае, скорее всего, имелось в виду выделение и освобождение ресурсов, что зачастую не принято делать в конструкторе/деструкторе. Лично я это поддерживаю.
Кстати, Вы случайно не с linux.org.ru? Манера общения похожая.
Кстати, Вы случайно не с linux.org.ru? Манера общения похожая.
+1
Выделение и освобождение ресурсов как раз следует делать в конструкторе и деструкторе. C++, особенно последних версий, имеет прекрасные языковые средства, позволяющие писать классы, устойчивые к необдуманному использованию.
В случае, когда экземпляру требуется дополнительная инициализация после создания, возникают дополнительные соглашения
[Чтобы использовать мой класс, надо вызвать последовательно Init3() и Init1(), но ни в коем случае не Init2(), она там для другого, понятненько?] между разработчиком класса и теми программистами, которые этот класс использует. Причем эти соглашения компилятор проверить не в состоянии.
Класс всегда должен быть в корректном состоянии, готовый к тому, что из него что-то вызовут. Если ресурсы получает не конструктор, этого добиться нельзя.
В случае, когда экземпляру требуется дополнительная инициализация после создания, возникают дополнительные соглашения
[Чтобы использовать мой класс, надо вызвать последовательно Init3() и Init1(), но ни в коем случае не Init2(), она там для другого, понятненько?] между разработчиком класса и теми программистами, которые этот класс использует. Причем эти соглашения компилятор проверить не в состоянии.
Класс всегда должен быть в корректном состоянии, готовый к тому, что из него что-то вызовут. Если ресурсы получает не конструктор, этого добиться нельзя.
+1
Что если Init() — виртуальный метод?
+1
Отдельного метода инициализации не должно существовать — все должен делать конструктор.
В случае наличия нескольких конструкторов, возможно либо использовать возможности C++11 (вызов конструктора из конструктора), либо сделать закрытый метод, в который убрать общую инициализацию.
Поведение конструктора как раз решает ту задачу, для которой делается виртуальный метод — переопределить поведение при инициализации экземпляра. И при этом, будут вызваны конструкторы всех родителей, что гарантирует их корректность.
А вот инициализация виртуальным методом данную задачу перекладывает на разработчика класса — ведь он с удовольствием забудет вызвать Init() у родителей, устроив таким образом похохотать коллегам, которые будут эту лапшу отлаживать.
В случае наличия нескольких конструкторов, возможно либо использовать возможности C++11 (вызов конструктора из конструктора), либо сделать закрытый метод, в который убрать общую инициализацию.
Поведение конструктора как раз решает ту задачу, для которой делается виртуальный метод — переопределить поведение при инициализации экземпляра. И при этом, будут вызваны конструкторы всех родителей, что гарантирует их корректность.
А вот инициализация виртуальным методом данную задачу перекладывает на разработчика класса — ведь он с удовольствием забудет вызвать Init() у родителей, устроив таким образом похохотать коллегам, которые будут эту лапшу отлаживать.
+1
Я обхожу это путем засовывания конструктора в приват/протектед, а наружу либо фабрика, либо фабричные методы и т.п.
+2
Посмотрите код внимательней, Init() и Cleanup() пришлось ввести потому что классы состояний — Одиночки, там один экземпляр класса и он статический, если так не делать, то они сразу будут занимать место в памяти. С другой стороны, если убрать Одиночек, то всю логику инициализации и удаления можно перенести в конструктор/деструктор.
+1
Ну, если убрать потенциальный источник проблем (Одиночек), дизайн только улучшиться.
Одиночки могут превратить приложение в такой неотлаживаемый глюкодром, что туши свет.
P.S. Я видел одиночку, разделяемого между плагинами и основным приложением. Это. Было. Чудовище.
Мое мнение — долой одиночек. Одних только танцев вокруг iostreams + dll достаточно, чтобы забыть про одиночек раз и навсегда.
Одиночки могут превратить приложение в такой неотлаживаемый глюкодром, что туши свет.
P.S. Я видел одиночку, разделяемого между плагинами и основным приложением. Это. Было. Чудовище.
Мое мнение — долой одиночек. Одних только танцев вокруг iostreams + dll достаточно, чтобы забыть про одиночек раз и навсегда.
+1
Я видел одиночку, разделяемого между плагинами и основным приложением. Это. Было. Чудовище
А в чём именно была проблема, если не секрет? Как я понимаю, доступ происходил через функцию?.. Там ведь главное память не трогать из других dll/из exe-файла — чтобы модули не лезли в чужие кучи. И что там с iostreams + dll плохого?.. Видимо, с хэндлами потоков данных какие-то чудеса, или нет?
+2
Упомяну главу про паттерн Состояние из известнейшей книги по игровым паттернам Game Programming Patterns. Там хорошо описаны плюсы и минусы, архитектурные решения. Использованный здесь стек состояний там называется Pushdown Automata. В этой главе он специально не упоминает паттерн Одиночка, потому что этот паттерн считается переоценённым, вместо этого предлагает, как вариант, хранить экземпляры конкретных состояний просто как static поле в базовом классе состояния.
+2
Sign up to leave a comment.
Управление игровыми состояниями в C++