Контекст: Архитектурная дисциплина вместо стандарта
В 2016 году команда Arkane Studios (Остин) внедрила строгий подмножество C++14, получившее в индустрии неофициальное название «арканутый C++». Ключевая философия подхода — разделение ответственности: компиляционное время (compile-time) остается максимально свободным для метапрограммирования, тогда как рантайм (runtime) жестко ограничивается для обеспечения предсказуемости производительности. Главная цель — zero frame allocations (отсутствие аллокаций памяти во время отрисовки кадра) и фиксированная цена вызова функций.
Что запрещено, а что разрешено
Команда отказалась от универсальности языка в пользу контроля. Запрещены механизмы, добавляющие скрытое поведение или усложняющие анализ потока управления. Разрешены инструменты, работающие на этапе компиляции, что позволяет перенести проверки на стадию сборки.
| Категория | Статус в Arkane C++ | Причина / Альтернатива |
|---|---|---|
| Исключения (try/catch) | ❌ Запрещены | Усложняют анализ потока управления; замена: явная проверка Result<T, E> |
| RTTI (dynamic_cast) | ❌ Запрещены | Скрытые накладные расходы; замена: статическая типизация и шаблоны |
| std::shared_ptr | ❌ Запрещены в хотпасе | Атомики и control block замедляют рендер; замена: value semantics или raw pointers с четким владением |
| if constexpr, std::span | ✅ Разрешены | Работают на compile-time, помогают проверять контракты до запуска |
| std::string_view | ✅ Разрешены | Избежание копирований строк без аллокаций |
Модель обработки ошибок: Result vs Panic
Вместо исключений используется тип Result<T, E>, который делит ошибки на два уровня. Это позволяет явно контролировать, где ошибка является частью логики, а где — критическим сбоем.
- Ожидаемые ошибки (Result): Файл не найден, разрыв соединения, исчерпание пула памяти. Вызывающий код получает ошибку и решает, как действовать дальше (например, повторить запрос или использовать данные по умолчанию).
- Нарушение контракта (Panic): Выход за границы массива, разыменование null, глобальный OOM. В этих случаях вызывается единая функция
panic(), которая пишет диагностику и аварийно завершает игру. Это предотвращает неопределенное поведение (UB) и упрощает отладку.
Для предотвращения игнорирования ошибок тип Result помечается атрибутом [[nodiscard]]. В проекте это было усилено: все предупреждения компилятора превращались в ошибки сборки, а попытка получить значение из Result без проверки приводила к panic() с выводом source_location.
Цена предсказуемости
Такой подход имеет свою цену. Из-за активного использования шаблонов и метапрограммирования время полной сборки движка и игры с нуля может достигать 4 часов. При изменении системных файлов рекомпиляция занимает около 15 минут. Однако это окупается стабильностью FPS и отсутствием «фризов» из-за сборщика мусора или аллокаций в критических секциях рендера.
Почему это важно
Пример Arkane показывает, что современный C++ — это не только новые фичи стандарта, но и архитектурные решения. Отказ от «удобных» абстракций (исключений, умных указателей) в пользу явного контроля позволяет создавать игры с высокой производительностью, где каждый байт и такт процессора на счету. Это пример того, как дисциплина кода может превзойти универсальность языка.
Источник: Habr ↗
