Pull to refresh

Comments 17

А готовых конечных автоматов в каком-нибудь Boost совсем нету?
Кстати, есть. Я когда-то смотрел — там есть вообще упоротая библиотека для супербыстрых машин состояний на темплейтной магии… Вот ссылочка с примером. Если честно, сам таким я бы пользоваться заопасался (правда, читал про неё пока мало).
Я вообще C++ опасаюсь пользоваться =). Но вроде выглядит нормально. Ну, во всяком случае примерно так же, как и куча другого непонятного Boost кода.
Оно нормально до тех пор, пока не начинается иерархичность машин состояний. Там дальше про иерархические машины рассказ идёт — и такая жажа начинается, что ховайся.

Вообще, меня постоянно озадачивает тот факт, что машину состояний не выносят в отдельный класс, объекты которого встраивают туда, где нужно эти состояния хранить (ну, а коллбеки байндят к вызовам класса через те же делегаты). Сам пишу игровой движок, пока застрял на уровне рендера, но когда доберусь до логики — обязательно на слои попробую разбить в этом смысле, чтобы понять в чём подвох.
Ну тут автор объясняет своё решение простотой.
У нас модель иерархическая и сложная, поэтому дерево лежит вообще отдельно. И каждое состояние — отдельный класс.
Зато бонусом идёт то, что на этой логике было сделано уже 4 проекта (в т.ч. не совсем похожие друг на друга).

Впрочем, в бэкэнде с этим проще — не надо никак отображать изменения, так что легко делать изолированный от всего конечный автомат.
Наличие методов Init() и Cleanup() определленно указывает, что автор плевать хотел на RAII.

-Получение экземпляра есть инициализация, разрушение — деинициализация!
-Не, не слышал…
В данном случае, скорее всего, имелось в виду выделение и освобождение ресурсов, что зачастую не принято делать в конструкторе/деструкторе. Лично я это поддерживаю.

Кстати, Вы случайно не с linux.org.ru? Манера общения похожая.
Выделение и освобождение ресурсов как раз следует делать в конструкторе и деструкторе. C++, особенно последних версий, имеет прекрасные языковые средства, позволяющие писать классы, устойчивые к необдуманному использованию.

В случае, когда экземпляру требуется дополнительная инициализация после создания, возникают дополнительные соглашения
[Чтобы использовать мой класс, надо вызвать последовательно Init3() и Init1(), но ни в коем случае не Init2(), она там для другого, понятненько?] между разработчиком класса и теми программистами, которые этот класс использует. Причем эти соглашения компилятор проверить не в состоянии.

Класс всегда должен быть в корректном состоянии, готовый к тому, что из него что-то вызовут. Если ресурсы получает не конструктор, этого добиться нельзя.
Что если Init() — виртуальный метод?
Отдельного метода инициализации не должно существовать — все должен делать конструктор.

В случае наличия нескольких конструкторов, возможно либо использовать возможности C++11 (вызов конструктора из конструктора), либо сделать закрытый метод, в который убрать общую инициализацию.

Поведение конструктора как раз решает ту задачу, для которой делается виртуальный метод — переопределить поведение при инициализации экземпляра. И при этом, будут вызваны конструкторы всех родителей, что гарантирует их корректность.

А вот инициализация виртуальным методом данную задачу перекладывает на разработчика класса — ведь он с удовольствием забудет вызвать Init() у родителей, устроив таким образом похохотать коллегам, которые будут эту лапшу отлаживать.
Я обхожу это путем засовывания конструктора в приват/протектед, а наружу либо фабрика, либо фабричные методы и т.п.
Ну, главная идея вами при этом сохраняется — продукция ваших фабрик — полностью готовые экземпляры, не так ли?

Фабрика чуть лучше конструктора, потому что она может иметь более явное и ясное название, нежели весьма безликий конструктор.
Посмотрите код внимательней, Init() и Cleanup() пришлось ввести потому что классы состояний — Одиночки, там один экземпляр класса и он статический, если так не делать, то они сразу будут занимать место в памяти. С другой стороны, если убрать Одиночек, то всю логику инициализации и удаления можно перенести в конструктор/деструктор.
Ну, если убрать потенциальный источник проблем (Одиночек), дизайн только улучшиться.
Одиночки могут превратить приложение в такой неотлаживаемый глюкодром, что туши свет.

P.S. Я видел одиночку, разделяемого между плагинами и основным приложением. Это. Было. Чудовище.

Мое мнение — долой одиночек. Одних только танцев вокруг iostreams + dll достаточно, чтобы забыть про одиночек раз и навсегда.
Я видел одиночку, разделяемого между плагинами и основным приложением. Это. Было. Чудовище


А в чём именно была проблема, если не секрет? Как я понимаю, доступ происходил через функцию?.. Там ведь главное память не трогать из других dll/из exe-файла — чтобы модули не лезли в чужие кучи. И что там с iostreams + dll плохого?.. Видимо, с хэндлами потоков данных какие-то чудеса, или нет?
Я отвечу уклончиво — все перечисленные вами проблемы там были. И другие тоже были. На этом, данную тему предлагаю свернуть как оффтопик.
Упомяну главу про паттерн Состояние из известнейшей книги по игровым паттернам Game Programming Patterns. Там хорошо описаны плюсы и минусы, архитектурные решения. Использованный здесь стек состояний там называется Pushdown Automata. В этой главе он специально не упоминает паттерн Одиночка, потому что этот паттерн считается переоценённым, вместо этого предлагает, как вариант, хранить экземпляры конкретных состояний просто как static поле в базовом классе состояния.
Sign up to leave a comment.