Локальные переменные
Этот документ определяет правила собственного C++-кода для инициализации переменных,
явного написания локальных типов, избыточного верхнеуровневого const и диагностики
использования после перемещения.
В движке нет отдельного правила «неизменяемость по умолчанию». Локальные переменные, параметры функций и методов следуют обычной семантике изменения C++ и не требуют аннотации при записи.
Инициализация через фигурные скобки
Каждое определение переменной инициализируйте непосредственно фигурными скобками,
где разрешён инициализатор: T name {expression};. Правило применяется к локальным
переменным, полям, глобальным, статическим и thread-local переменным. Для начального
состояния по умолчанию используйте пустые скобки, в том числе для объектов классов:
int32_t count {};, string text {};.
У инициализатора при объявлении есть два исключения:
- Поле класса, инициализируемое его конструкторами, может не иметь инициализатора в объявлении. Каждый применимый конструктор должен инициализировать его до использования.
- Локальную переменную разрешено намеренно инициализировать позже. На каждом пути управления ей должно быть присвоено значение до первого чтения; эта гарантия должна быть видна из кода.
Сохраняйте выбор конструктора, состав и размер контейнеров, выведенные типы и проверки
преобразований. Скобки могут выбрать перегрузку initializer_list: замена
vector<int32_t> values(count) на vector<int32_t> values {count} превращает размер
в элемент. Явно создайте нужное значение и используйте его внутри инициализатора
с фигурными скобками. Для сохранённого выведения используйте auto value {expression};;
auto value = {expression}; выводит список инициализации.
Именованный scoped guard создавайте непосредственно, если его конструктор несёт
аннотации захвата Clang Thread Safety Analysis: scoped_lock locker {bufferMutex};
или ServerConnection::OutBufAccessor output {connection, NetMessage::Ping};.
При инициализации через auto guard {factory()}; или auto guard {Guard {args}};
анализатор может потерять связь с захватом и сообщить об освобождении незахваченной
блокировки. Существующие временные guard полного выражения сохраняйте временными;
именованный guard во внешнем блоке продлевает время удержания блокировки.
Если результат try-lock сохраняется для последующего условия, используйте
bool acquired {};, затем acquired = lock->TryLockStateMutex();. При
инициализации bool фигурными скобками Clang может потерять связь с try-acquire.
Сохраняйте прежний порядок проверки и освобождения блокировки.
Правило и два исключения проверяются при просмотре исходников. Описанные ниже анализаторы сохраняют прежнюю область типов, константности и использования после перемещения.
Избыточный локальный const
Не добавляйте верхнеуровневый const к автоматической локальной переменной,
если его удаление не меняет контракт типа:
int32_t count {GetCount()};
item_ptr item {FindItem()};
Сохраняйте константность, являющуюся частью фактического типа или семантики значения:
const Item* item {FindItem()}; // const pointee
const Item& item {GetItem()}; // const referent
const int32_t values[] {1, 2}; // const elements
constexpr int32_t limit {10}; // required by constexpr
У int32_t* const pointer верхнеуровневый const можно удалить, поэтому он
диагностируется. Параметры эта проверка не рассматривает.
Редкое намеренное использование верхнеуровневого квалификатора локальной переменной можно подавить в строке диагностики или предыдущей строке:
const int32_t value {SelectOverload()}; // FO_REDUNDANT_CONST_SUPPRESS: const is required for overload selection
Причина обязательна и должна содержать не менее восьми символов.
Явные простые локальные типы
Заменяйте локальный auto, только если выполняются все условия:
- Clang определяет одно доступное неквалифицированное имя типа в
snake_case. - У типа нет видимых аргументов шаблона.
- Тип не вложен и не квалифицирован пространством имён.
- Явное написание типа не добавляет и не скрывает преобразование.
- Объявление не является structured binding, лямбдой или зависимым выведением.
Когда Clang показывает только канонический шаблонный тип, неквалифицированный
псевдоним допустим, если он явно написан в инициализаторе, является единственным
простым псевдонимом того же развёрнутого типа и доступен в месте объявления.
Это покрывает вызовы вроде glm::translate(mat44 {...}, offset), не допуская
внутренние имена библиотек наподобие iterator или value_type.
Примеры:
int32_t count {GetCount()};
float32_t factor {GetFactor()};
hstring name {GetName()};
Сохраняйте auto в случаях вроде:
auto values {vector<int32_t> {}};
auto iter {values.begin()}; // nested iterator type
auto handle {MakeHandle<void>()}; // template spelling
auto pointer {GetRawPointer()}; // explicit target may convert
auto [left, right] {Split(value)}; // structured binding
Если выведенный числовой примитив записывается явно, используйте размерное имя движка:
| Выведенный тип | Явное написание |
|---|---|
short |
int16_t |
int |
int32_t |
float |
float32_t |
double |
float64_t |
Существующие знаковые и беззнаковые типы фиксированной ширины сохраняют своё написание.
См. Контроль соблюдения, где описана проверка правила.
Использование после перемещения
Перемещение из локальной переменной не делает её изменяемой и не требует
аннотации. Независимый gate bugprone-use-after-move запрещает последующее
использование до корректной повторной инициализации:
value_type value {BuildValue()};
Consume(std::move(value));
Намеренная проверка перемещённого объекта должна явно назвать контракт:
CHECK(value.empty()); // FO_USE_AFTER_MOVE_SUPPRESS: test verifies the moved-from container contract
Причина обязательна и должна содержать не менее восьми символов.
Контроль соблюдения
Правила типов, константности и использования после перемещения используют анализатор
с compile_commands.json для Clang 20+:
проверку явных типов и проверки clang-query / clang-tidy для избыточной
локальной константности и использования после перемещения. Движок владеет
только описанными правилами и маркерами FO_REDUNDANT_CONST_SUPPRESS /
FO_USE_AFTER_MOVE_SUPPRESS. Реализация анализатора, создание базы компиляции,
выбор области и политика CI принадлежат подключающему проекту.
Перед публикацией выполняйте все три проверки над изменёнными исходниками движка. Проект может распространить тот же gate на собственные native extensions, но пути проекта, имена задач и workflow jobs не входят в переиспользуемый контракт.
На машине, запускающей gate, должны быть доступны и clang-query, и
clang-tidy, а база компиляции должна обеспечивать настоящий разбор.
Сгенерированные заголовки из общей цепочки include движка должны существовать
до анализа, иначе translation units завершатся на отсутствующих include ещё до
проверки правил.
До передачи базе Clang tooling сократите compilation database до одной команды на translation unit. CMake создаёт entry для каждого target, компилирующего файл, поэтому один Engine source может встретиться десятки раз. Clang выполняет action для каждой entry, а clang-query удерживает каждый построенный AST, и duplicates умножают wall time и память. Один Engine AST занимает примерно половину гигабайта; существенно больший расход обычно означает непрореженную database, а не неизбежную стоимость analyzer.