Перейти к содержимому

RAII (Resource Acquisition Is Initialization)

Описание

Рекомендуется использовать идиому RAII для управления ресурсами. Ресурс должен захватываться при создании объекта и автоматически освобождаться при его уничтожении.

Это относится к памяти, файлам, потокам, мьютексам, дескрипторам ОС, объектам SDL/HID, графическим ресурсам и любым другим объектам, требующим явного освобождения.

WARNING

Если какой-либо тип ресурса используется всего один раз в контексте всего движка, создание отдельной RAII-обёртки не является обязательным. В таких случаях допускается ручное управление ресурсом, если это не ухудшает читаемость и сопровождение кода.

Когда использовать

Используйте RAII при работе с:

  • памятью
  • файлами
  • потоками
  • мьютексами
  • графическими API

Преимущества

  • ✅ Автоматическое освобождение ресурсов
  • ✅ Безопасность при раннем выходе из функции
  • ✅ Меньше вероятность утечек памяти и ресурсов
  • ✅ Код становится проще и легче поддерживается

Группировка функциональности (Class vs Namespace)

Описание

Рекомендуется группировать связанную функциональность в namespace или class, избегая большого количества глобальных функций.

Выбор между ними определяется наличием состояния.

  • Используйте namespace, если функции являются независимыми, не требуют хранения данных и представляют собой набор утилит
  • Используйте class, если объект обладает состоянием, управляет ресурсами или описывает отдельную сущность, менеджер, сервис, etc

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

WARNING

Исключением являются специальные классы, разделяющие ответственность между несколькими подсистемами. Например, CGamepadService управляет устройствами, а обработкой пользовательского ввода занимается отдельная система CInput.

Когда использовать

Используйте namespace

  • математические функции
  • утилиты
  • функции преобразования
  • сериализацию
  • алгоритмы без внутреннего состояния

Используйте class

  • сервисы
  • менеджеры
  • игровые сущности
  • объекты с жизненным циклом
  • классы, владеющие ресурсами
  • объекты, хранящие состояние

Преимущества

  • ✅ Логически связанный код находится в одном месте.
  • ✅ Минимизируется количество глобальных функций.
  • ✅ Проще сопровождать и расширять функциональность.
  • ✅ Становится очевидно, кто отвечает за выполнение конкретной операции.

Недостатки

  • ❌ Излишняя инкапсуляция иногда усложняет повторное использование небольших функций

Унифицированные интерфейсы

Описание

При добавлении альтернативной реализации существующей системы рекомендуется использовать общий интерфейс.

Например, если движок поддерживает растровые и векторные изображения, обе реализации должны предоставлять единый интерфейс для загрузки, освобождения и доступа к ресурсу.

Это позволяет изолировать различия между реализациями и избежать большого количества if, switch и #ifdef, разбросанных по коду.

WARNING

Если различия между реализациями слишком велики и не позволяют построить понятный общий интерфейс, допускается использование отдельных API. Не стоит создавать абстракцию ради самой абстракции.

Когда использовать

  • альтернативные реализации одной подсистемы
  • различные форматы ресурсов
  • платформозависимые реализации
  • взаимозаменяемые сервисы

Преимущества

  • ✅ Код не зависит от конкретной реализации
  • ✅ Минимизируется количество ветвлений (if, switch, #ifdef)

Изоляция платформозависимого кода

Описание

Не используйте платформозависимые API, типы данных и заголовочные файлы в игровых, рендерных и других платформонезависимых модулях.

Весь платформозависимый код должен находиться в соответствующих Platform-реализациях (Windows, Linux, macOS и т.д.) и предоставлять единый интерфейс для остальной части движка.

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

WARNING

Использование #ifdef допускается только внутри платформенных модулей или при отсутствии разумной возможности вынести различия за общий интерфейс.

Когда использовать

  • работа с файловой системой
  • создание окон
  • управление потоками
  • системные вызовы
  • взаимодействие с ОС

Преимущества

  • ✅ Игровой код не зависит от конкретной платформы
  • ✅ Упрощается поддержка новых платформ
  • ✅ Значительно сокращается количество #ifdef
  • ✅ Платформенная логика сосредоточена в одном месте

Недостатки

  • ❌ Требуется поддерживать отдельные реализации для каждой платформы

Использование внутренних типов

Описание

При разработке рекомендуется использовать внутренние типы и псевдонимы IXR вместо прямого использования типов стандартной библиотеки или платформозависимых типов.

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

Например:

  • std::stringxr_string
  • std::string_viewxr_string_view
  • std::unordered_mapxr_hash_map
  • std::unordered_setxr_hash_set
  • std::vectorxr_vector
  • std::unique_ptrxr_unique_ptr
  • std::uint32_tu32

WARNING

Если для используемого типа отсутствует внутренний аналог, допускается использование типа из стандартной библиотеки.

NOTE

В коде, который может выполняться до инициализации внутренних аллокаторов движка (например, при запуске приложения или обработке исключений), следует использовать контейнеры стандартной библиотеки (std::*). В этот момент внутренние типы IXR могут быть ещё недоступны.

Когда использовать

  • контейнеры STL
  • строковые типы
  • целочисленные типы фиксированной длины
  • умные указатели
  • другие типы, имеющие внутренние аналоги

Преимущества

  • ✅ Единый стиль кода во всём проекте
  • ✅ Возможность централизованно изменять реализацию внутренних типов
  • ✅ Упрощается переносимость и сопровождение проекта

Опубликовано под лицензией MIT.