
В 2016 году EA закрывает свой последний оплот разработки в Питере, и народ начал разбредаться по разным студиям и командам. Тогда мировой игрострой ещё благосклонно смотрел на обладателей красных паспортов, и несколько моих бывших коллег «со связями» организовали мини‑галеру и умудрились получить контракт на поддержку движка для прототипа новой игры Arkane. Причем схема взаимодействия была достаточно мутная, из‑за нежелания Остинцев работать напрямую с пост‑СНГ конторами, поэтому основной контракт у них был через каких‑то корейцев за три рубля, которые делегировали часть работы французкой конторе за рубль, а те наняли каких‑то ребят за десять копеек из восточной Европы, ну вы поняли каких:)
Нас почти месяц мучили устаревшей на пару лет документацией к собственному движку, примерами с ошибками в коде, падающими тестами и устными экзаменами со сдачей домашек, но в конце концов этот учебный ад закончился и команду допустили к офицерскому телу... я хотел сказать, к движку и самой игре.
С этого момента и началось моё знакомство с идеями, которые мы внутри команды прозвали «арканутым C++», и с их реальным применением в разработке игр. Я и раньше сталкивался с жёсткими требованиями к разработке на плюсах на своей доигростростроевской работе, но за шесть лет как‑то раздобрел и перестал сильно жестить с оптимизацией, так что знакомство с командой из Остина вызвало стойкое дежавю разработки начала нулевых.
Я не собираюсь ругать или рекламировать конкретный подход, а просто хочу показать, насколько разными бывают люди, команды и движки внутри одной индустрии и какую цену иногда приходится платить за предсказуемость и перф.
C++ язык многоуровневый, и он умеет в исключения, жонглирует RTTI, подкупает лёгкостью виртуальных функций и прозрачностью шаблонов, владеет пятью стилями кунг‑фу умных указателей, а ещё, видимо, имеет контейнер на любой случай жизни. Однако в хотпасе эта универсальность быстро превращается в проблемы, которые приходится решать рано или поздно и все сводится к нескольким простым вещам (эта функция выделяет память? Здесь может вылететь ошибка? Кому принадлежит объект? Откуда взялся malloc, когда внутрь передали готовый буфер?), заставляя вас писать максимально быстрый код, и заметьте я не сказал современный...
Команда из Остина взяла «тепленький, с пылу с жару» на тот момент C++14 и запретила значительную его часть, в результате чего получился скорее не новый язык, а архитектурная дисциплина с аллергией на аллокации во время кадра (zero frame allocations) и с предсказуемой ценой вызова, причём compile‑time часть языка осталась почти без ограничений, тогда как рантайм, наоборот, зажали до максимально предсказуемого подмножества.
Ниже будет про то, что именно запрещали, чем заменяли запрещённое, сколько за это приходилось платить и почему применять это надо осторожно, но... как обычно, всегда есть но... люди, использовавшие такую архитектуру, создали удивительные игры — умные, красивые и очень необычные.
В крупном проекте почти никогда не существует единого C++, потому что есть C++ в тулах, где важнее скорость разработки и есть C++ загрузчика, где можно один раз выделить гигабайт временной памяти и забыть. Еще есть C++ игровой логики, где удобный shared_ptr может быть вполне нормальным выбором, и есть хотпас рендера, где тот же shared_ptr добавляет атомики, отдельный control block и непонятный момент освобождения памяти, который зависит от последнего неизвестного владельца и, вообще-то, arkanec++ предназначен только для последней области.
Если заставить функцию конвертации ассетов принимать аллокатор через каждый конструктор, проект не станет быстрее... ну, может совсем чуточку, но программисты точно начнут чаще ходить пить кофе, а может чего и покрепче, чтобы отдохнуть от этой архитектуры и полечить расшатавшиеся нервишки. В проекте же легким движением руки, это все было распространено на всех.
Архитектура движка оставляла от С++ почти все, что работало на этапе компиляции, вроде if constexpr, std::span, std::string_view, и source_location, которые приветствовались, ибо помогали компилятору и команде проверить больше до запуска (Я буду писать общеизвестные аналоги, чтобы не уходить в дебри собственных велосипедов, благо что все эти идеи так или иначе доехали до стандарта).
Исключения и RTTI запрещаются, потому что добавляют поведение, которое сложнее увидеть в точке вызова, да шаблонное колдовство тоже не приветствуется, но уже по причине, что код выполняется быстро, зато компилируется вечность. Вечность в нашем случае означала почти четыре часа компиляции движка и игры с нуля (сам движок + уровни игры + шейдеры), если репа была пустая и порядка пятнадцати минут рекомпила, если что‑то тронуть в условно «системных файлах». Но в домашних условиях движок обычно не собирали, забирая уже готовые бандлы с билдфермы.
На современных ABI непойманное исключение почти ничего не стоит в нормальной жизни, а таблицы раскрутки лежат где‑то и ждут своего часа, но проблема с исключениями проявляется, в усложнении анализа потока управления и на границах кода и вместо редкой и дорогой раскрутки стека появляется постоянная явная проверка результата, внося небольшие накладные расходы, которые можно регулировать при необходимости.

Ошибка бывает либо рабочей, либо последней
Все проблемы в runtime делились на два уровня, первый описывал ожидаемые неприятности окружающего мира, когда файла нет, соединение оборвалось, данные повреждены или закончился локальный аллокатор. Да, бывало что и закончился, но программа при этом не сломана, просто обстоятельства вызова оказались неудачными, поэтому вызывающий код получает свою ошибку вида Result<T, E> и решает, что делать дальше, как‑то так:
``` файл не найден -> Result сокет закрыт -> Result плохой пользовательский ввод -> Result bounded pool исчерпан -> Result индекс вне массива -> panic сломанный FSM -> panic разыменование пустого значения-> panic память совсем кончилась -> panic ```
Второй уровень это уже нарушение контракта, например выход за границу массива, невозможное состояние автомата, разыменование пустого значения или глобальный OOM, и поскольку продолжать работу после такого события опасно, вызывается единый panic(), пишется диагностика и игра убивается, приводя QA в неописуемый восторг и желание оформить баженцию в Jira.
Но проблема была не в самом Result, которых программисты понаписали уже несколько сотен видов под любые случаи, а в запрете проигнорировать результат, для чего тип помечался [[nodiscard]], ошибка распространялась ранним возвратом, а успешный путь оставался прижатым к левому краю без пирамиды из if.
пирамида if ранний возврат do_a() r = do_a() if ok: if fail: return Err do_b() r = do_b() if ok: if fail: return Err do_c() r = do_c() if ok: if fail: return Err success success else: Err else: Err else: Err happy path уезжает вправо happy path остаётся слева
У стандартного std::expected сейчас почти та же модель, но тогда команда хотела контролировать всё самостоятельно, вследствие чего попытка взять отсутствующее значение шла через собственный panic() с аналогом source_location, а требование обработать результат висело на всём типе независимо от реализации стандартной библиотеки.
Слово «требование» здесь надо понимать в контексте использования, поскольку на[[nodiscard]] обычный компилятор выдаёт только ворнинг, а не физически запрещает сборку, поэтому там это дело подшаманили ассертов и заодно все ворнинги пометили как ошибки, ну чтобы вы с ними гарантированно проект собрать не могли.
std::expected Result .value() .value() | | v v std::terminate / abort panic(source_location) (зависит от STL и флагов) файл + строка известны [[nodiscard]] [[nodiscard]] на отдельных методах на всём типе не везде всегда, независимо от STL
Естественно за все приходится платить, и каждый такой возврат несёт дополнительную проверку, каждый уровень проверяет её и ранний выход увеличивает код, то есть это не бесплатная обработка ошибок и мы обменяли почти бесплатный средний случай и дорогой хвост на постоянный налог на перф даже в релизе, зато получив предсказуемое худшее время.

Каждый игронутый желает знать где сидит байт, наверное...
Понятие zero frame allocations знакомо разработчикам игр, но пользуются этим принципом достаточно лениво и далеко не все из‑за его организационной цены. Хотеть, конечно, можно чего угодно, однако сроки, руководство и новые люди в команде, которых надо переучивать, довольно быстро возвращают архитектуру на землю.
Но техдира остинской команды в детстве, похоже, сильно покусали статические анализаторы, натренированные на коде Эктона и Кармака, отчего нелюбовь ко всему динамическому сквозила даже из документации, где карте памяти была посвящена едва ли не треть всей вики.
Собственно у ребят из команды, которые принимали ревью, это тоже было одним из главным пунктиком при анализе, ибо заразно как ковид. Но... тут я могу только похлопать, в движке они смогли довести число аллокаций за кадр где‑то до двадцати, т.е два десятка аллокаций на фрейме... не тысяч - десятков. Чем очень гордились и тыкали бывших симсоводов носиком, когда те таки умудрились пропихнуть парочку новых через ревью, причём все крупные и происходящие на старте кадра.
Заплатить за это пришлось громадным стеком под двадцать мегабайт для главного потока (+ 2Мб на воркерах * число ядер на машине) и жёстким расталкиванием всего, что только можно, по статик кешам, буферам и пулам. Так что любое покушение на рантайм‑выделение памяти просто не проходило мерж‑тесты и не позволяло отправить изменение на ревью.
Правило формулировалось так, что глобальные new и delete запрещены, а любой тип, которому надо выделять память, принимает аллокатор явно. Системные аллокаторы сейчас очень неплохи, а реализации вроде mimalloc, rpmalloc и jemalloc вообще способны перемалывать огромное количество выделений памяти достаточно быстро, но даже там проблемы все равно есть, просто смещаются в несколько другие области. Глобальная куча точно вам не ответит, какой подсистеме принадлежит память, каков её бюджет и когда всё выделенное можно освободить, частично или полностью. Да и с разделением на домены там серьезные проблемы.
Доменная память была еще одним местным каргокультом, не скажу что мне сильно нравится именно такой подход для работы с памятью, но он сильно повышает вероятность долго работать на консолях. Если система частиц получает скрэтч‑аллокатор кадра, все её временные данные исчезают одним сбросом указателя, или в сетевом обработчике пул превращает утечку не в смерть всего процесса, а в локальное исчерпание домена (пула, кеша, аллокатора), после которого его можно сбросить, недостающие данные пересобрать и продолжить работу. Еще отдельный домен памяти для загрузчика уровня позволяет профилировщику показать его настоящий пик, а не общую гору аллокаций неизвестного происхождения.
общий системный heap | кто выделил? сколько можно? когда чистить? | ? FrameArena StagePool LevelHeap | | | частицы пакеты и очереди данные уровня команды кадра лимиты отдельный пик временные данные локальный OOM очистка целиком | | | reset каждый кадр сброс и пересборка unload уровня
Естественно стандартные контейнеры при этом превращаются в тыкву... Но не обязательно выбрасываются и тот жеstd::vector можно завернуть в самописный адаптер, который отправляет память в нужный домен, но у такого адаптера надо явно определить поведение при копировании, перемещении и swap, нагружая использование дополнительной работой, что требует и времени и людей для сопровождения.
Еще естьstd::pmr, (сейчас есть, в 2016 был только пропозал и стыренные у boost`a inplace реализации), но у стандартной реализации memory_resource будет виртуальный вызов на каждую аллокацию (это не видно на фоне цены аллокации в обычном коде), а аллокатор как параметр шаблона можно встроить на этапе компиляции.
Практически же значение зависит от нагрузки и если система делает одну большую аллокацию и потом миллион раз обходит массив, цена виртуального вызова просто исчезает в шуме, но если она создаёт тысячи мелких объектов, проблема уже не только в vtable, а в самих тысячах мелких объектов. Поэтому обычная логика конверировалась в проходы по массиву, чтобы вытолкнуть даже дешевые аллокации и контейнеры из цикла, что конечно читабельности не добавляло.

Сторонняя библиотека всё равно вызовет malloc
Как ни старайтесь, но полный контроль памяти останется очередной красивой идеей и будет работать только до подключения первой сторонней библиотеки, из‑за чего в репозитории встречались переписанные или адаптированные версии OpenSSL, ICU, zlib и даже отдельных частей платформенных либ из SDK. Не берусь судить, насколько это красиво и удобно для сопровождения, но в игровые тулы со своим самоваром не ходят, как говорится, поэтому оставлю решение на совести техлюдей студии.
Хорошо, если у библиотеки есть официальные хуки, вроде zalloc и zfree у zlib или функций настройки аллокатора у OpenSSL, тогда можно их сразу затюнить на свой аллоактор, а при отсутствии хуков зависимость можно вынести в отдельный worker или процесс с ограниченным бюджетом памяти, переведя общение на сообщения.
Обычно такие танцы с бубном обходятся дорого и вместо обычного вызова появляется асинхронный обмен, сериализация данных, очередь, обработка таймаутов и отдельный жизненный цикл, зато библиотека больше не способна незаметно вам подгадить... в теории.
игра | | request v очередь сообщений --> изолированный worker --> сторонняя библиотека ^ | | result +-- heap limit | +-- watchdog `-------------------------+-- restart
И пару раз это действительно спасало, например стандартный платформенный модуль стора для xbox понемногу подъедал память во время работы, поэтому его посадили в изолированный worker‑процесс с кучей около мегабайта + буфер (буфер был нужен если гадкий модуль все-же что-то успел записать за границу данных), и когда бюджет заканчивался воркер стандартно крашился, после чего watchdog пересоздавал worker и всё начиналось заново.
Не делайте так... это какая‑то черная магия, если честно и подобная схема работает только при контролируемом завершении воркера либо при настоящей изоляции в отдельном процессе, иначе вместо архитектуры получится лотерея с use‑after‑free. Собственно лотерея там и случилась, поэтому перед релизом буфер немного увеличили, потому что пару раз запись перехлёстывала даже через него.
Можно использовать любой контейнер, если он вектор
Стандартную библиотеку применять разрешалось, но в основном ради алгоритмов, а большинство контейнеров были переписаны или адаптированы к векторам. Классический std::list выглядит последовательностью элементов только в исходниках, а для процессора это будет набором случайных адресов, по которым надо прыгать, каждый раз ожидая следующую кешлинию. В std::unordered_map использует бакеты и ноды, что тоже требует нескольких косвенных обращений при поиске.
Из стандартных контейнеров спокойнее всего чувствовали себя разновидности массивов, а основным требованием к движковым структурам были вектороподобность, что естественно подводило к использованию SoA вместо AoS и в некоторых местах к сильному дроблению объекта на составные части.
AoS [pos vel hp type name][pos vel hp type name][pos vel hp type name] \____________ cache line ____________/ нужны только pos и vel, остальное приехало зря SoA [pos][pos][pos][pos][pos][pos][pos][pos] [vel][vel][vel][vel][vel][vel][vel][vel] [hp ][hp ][hp ][hp ][hp ][hp ][hp ][hp ] cache line содержит только данные текущего прохода
Это однако не означало, что SoA всегда лучше и если код в каждой итерации читает все поля объекта, отдельные массивы наоборот усложнят адресацию и добавляют походов в память и наоборот надо было комбинировать данные вместе по логическому использованию, а не абы какие данные, которые бы красиво смотрелись вместе в объявлении структуры.
Да и часть алгоритмов STL тоже были переписаны, а сортовsort`a реально было штук десять и у каждого отдельный комментарий когда его лучше использовать и с каким контейнером.
iostream, конечно же, был выкинут вообще весь и замены ему не было. Выкинули из‑за размера, статической инициализации и тяжёлого интерфейса и вообще работа с потоками вывода была очень и очень ограничена, а вместо это использовались аналоги format и print на статических буферах.
Что однако породило другую проблему, потому что некоторые оставленные части STL умеют сообщать об ошибках только через исключения, и тот‑же vector::push_back может получить bad_alloc, или конструктор thread и mutex::lock могут получить system_error.
При выключенных исключениях эти пути обычно заканчиваются terminate или abort, причём точное поведение зависит от реализации, локальное исчерпание аллокатора, которое собственный контейнер мог бы вернуть как Result, внутри std::vector внезапно становится panic и крашит игру. Можно конечно написать и собственный vector, и собственный mutex и собственное форматирование, но тогда архитектурно игра незаметно превращается в собственную стандартную библиотеку, которую тоже кто‑то должен поддерживать.

Лучше ли это обычного С++?
Не лучше, просто другой, и я знаю нескольких людей из автомобильного софта, которые и не переставали жить в таком С++, и там это, думаю, оправдано, ибо ценой ошибки вполне может стать чья‑то головушка. Просто следует помнить, что use‑after‑free магическим образом не вылечится, а собственный аллокатор не помешает команде стабильно клепать баги с записью за конец буфера, да и плотная хешмапа не сделает гонку по данным менее болезненной, и вообще писать обычные ошибки это так по‑человечески.
Такая архитектура не делает язык безопасным и не гарантирует, что программа станет быстрее. Иногда она действительно была быстрее, но и код вокруг уже был быстрым, но большей частью Result просто раздувал код, а кастомная хешмапа проигрывала стандартной на конкретных наборах данных. Ну и аллокатор, протащенный через половину API ради двух аллокаций, которые удалось убрать из кадра... как говорится, месье знает толк в удовольствиях.
Главным выигрышем я бы назвал даже не скорость, а её объяснимость, поскольку профилировщик тут действительно становится рабочим инструментом и перестает быть снимком непонятной фигни, которая неведомым образом получилась на фрейме. У пика памяти можно найти хозяина, а у ошибки заранее определить, можно после неё жить или пора убивать процесс, и весь фрейм становится не просто нагромождением обработчиков, вызванных в рандомные моменты, а начинает работать по вполне понятным законам. Но какой ценой...
Для рендера, физики, микшера и job system это оправданно, но тащить её в редактор, импортёр FBX, launcher и всю игровую логику уже за гранью добра и зла, и больше похоже на религиозный культ, от которой проект больше терял, чем получал. В Остине построили свой маленький и злой ++C внутри большого и пушистого С++, и этот внутренний язык запрещал худшие по времени пути программы, но требовал шумных API, собственных контейнеров и постоянного обучения новых разработчиков, так что время кадра покупалось временем жизни программиста.
Для девяти проектов из десяти такое надругательство над языком окажется ненужным, дорогим и откровенно вредным, зато оставшемуся одному она поможет выпустить игру, которая лагает незаметно для вас, как сказал бы доктор Кармак — «все лагают».
Почему пчелы? Есть такая старая шутка, что если посмотреть на пчелу с точки зрения аэродинамики, то она и летать не очень‑то и должна, ибо тело тяжёлое, а крылья маленькие, посадочные опоры короткие и хвост перевешивает, но пчела ничего не понимает в аэродинамике и поэтому летает... как умеет так и летает.
Пошёл бы я ещё раз в проект с арканутым C++? Скорее нет, ибо UE в умелых руках работает не сильно медленнее, но экономит сотню‑другую часов своей обычностью... но с другой стороны, где еще увидишь насколько далеко может зайти студия в погоне за перфом.
А как делать правильный С++ обсудим на вебинаре PVS

Комментарии (32)

OlegZH
08.09.2026 06:06В Остине построили свой маленький и злой ++C внутри большого и пушистого С++, и этот внутренний язык запрещал худшие по времени пути программы, но требовал шумных API, собственных контейнеров и постоянного обучения новых разработчиков, так что время кадра покупалось временем жизни программиста.
Неужели и вправду нельзя создать отдельный язык (вроде ++C), компилятор которого уже "из коробки" имеет все необходимые ограничения?

Kromster80
08.09.2026 06:06У меня такой же вопрос был по Delphi - чтобы часть инструкций (типа with и goto) были заблокирована на уровне компилятора (сторонние либы, чтобы не трогать, примем что будут pre-compiled). Но почему-то такого так и не изобрели.

HemulGM
08.09.2026 06:06Потому что, как ты сам и сказал, есть проблема со сторонними библиотеками, а себя (свой код) ограничивать ты можешь и сам. В итоге получается инструмент, который позволяет тебе ограничить только себя, при этом, случайно, ты сам, и так "запретные" конструкции вряд ли напишешь.

evgenstf
08.09.2026 06:06По большей части rust — ровно то, что вы описали. Он из коробки поддерживает нужную модель ошибок, закрывает почти все баги памяти (в safe коде) и много чего еще хорошего дает, например модель синхронизации через send/sync трейты.
Правда их требования к аллокации так легко не решились бы, нужно было бы так же кастомные контейнеры писать.

amhbr
08.09.2026 06:06Да, да, rust, еще бы не заставлял писать то, что компилятор может написать сам.

domix32
08.09.2026 06:06Например?

amhbr
08.09.2026 06:06Достаточно посмотреть на избыточность синтаксиса многопоточности, времен жизни, заимствований, обработки ошибок. А главное, при изменении кода, нужно будет исправлять вручную.

domix32
08.09.2026 06:06Так "избыточность" синтаксиса возникает ровно по причине того что компилятор не может угадать, что требуется дописать, о чем успешно рапортует. В частности расстановка времен жизни и прочих ограничений типов. Но да, рефакторинг боль.

amhbr
08.09.2026 06:06Так "избыточность" синтаксиса возникает ровно по причине того что компилятор не может угадать, что требуется дописать, о чем успешно рапортует.
Вот и я об этом же, компилятор rust не может. Есть другие языки, компилятор которых это делает самостоятельно. Может и в rust, когда-нибудь, появится.

domix32
08.09.2026 06:06Это какие другие компиляторы осиливают самостоятельно выводить гарантированное время жизни и жить без GC при этом? ЕМНИП до раста не существовало в принципе ни одного языка, который бы избавлялся от многопоточных дедлоков на этапе компиляции без ручного добавления килобайтов кода синхронизациия на поток. GHC тащит свой рантайм и даже близко несопоставим с остальными компилируемыми языками, да и чтобы приблизится к уровню раста там столько бойлерплейта надо дописывать. vlang не так давно что-то там добавлял про заимствования и кажется у D было что-то там про управление временем жизни и заимствований, но опять же - до уровня Rust им определённо далеко и я сомневаюсь, что они достаточно самостоятельны в этом плане. В плюсах там на диких костылях что-то собирали, что потом пару часов компилируется, а больше кандидатов у меня чё-то и нет. Так про какие иные компиляторы речь?

Siemargl
08.09.2026 06:06раста не существовало в принципе ни одного языка, который бы избавлялся от многопоточных дедлоков на этапе компиляции
раст тоже этого не умеет. в нем те же самые примитивы.
и вообще задолбали недоязыком

domix32
08.09.2026 06:06Как-то так Jonathan Blow и написал себе Jai. Ну и odin как тёзка по синтаксису туда же.

CrashLogger
08.09.2026 06:06Написание своего компилятора - это намного более сложная задача, чем разработка игры. Мало кто будет так заморачиваться.
Fedorkov
В автопроме это по сути делается не ради безопасности, а чтобы формально снять ответственность с руководителей проекта.
https://repository.tudelft.nl/record/uuid:646de5ba-eee8-4ec8-8bbc-2c188e1847ea
Я сам работал по MISRA C++, и меня не покидало ощущение, что все эти пляски с бубном только увеличивают риск допустить ошибку (как минимум из-за выросшего объёма кода).
amhbr
Так многие требования там вполне адекватные для повышения надежности и детерминированного поведения, что требует некоторого количества "лишнего" кода. Если бы надежный код было проще писать, то и "ширпотребное" ПО выглядело иначе.
Fedorkov
Ну так и оставили бы адекватные требования. Зачем, например, требовать городить лесенку из
if(...) { if(...) { if(...) {вместо раннего return? Это же только вредит читаемости кода, и, как следствие, увеличивает риск человеческой ошибки.eao197
Для некоторых разработчиков лесенка из if-ов + единственный return из функции делает код более читаемым, чем множество if-ов с ранними return-ами. Так что это субъективно.
amhbr
В реальном коде не обязательно жестко соблюдать 100% всех рекомендаций, да и таблица требований обязательно/желательно/... обычно описывается под конкретное применение, т.к. особенности области использования всегда есть и это нормально.
Единственную точку выхода можно организовать различными способами (не только "лесенку из
if"), если это является обязательным.Общий смысл в повышении детерминированности кода. В этом могли бы быть полезны "чистые функции" (в понимании встроенных систем), только на C/C++ их нормально не сделаешь, нужен внешний кодогенератор (что, обычно, не приветствуется) или нормальное метапрограммирование (а не обрубок на шаблонах).
Fedorkov
Конкретно это правило помечено как "required", и его нарушение означает, что код не соответствует стандарту: "C++ code that is claimed to conform to MISRA C++ shall comply with every “Required” rule". https://github.com/zaznov/MISRA/blob/main/MISRA%20C%2B%2B%202008.pdf
Как? Наименее корявая известная мне альтернатива — вынести содержимое внутреннего if в отдельную функцию — нарушает локализацию логики (нужно больше бегать глазами, чтобы прочитать код) и на ровном месте создаёт лишнее зацепление (вторая функция существует только ради вызова из первой).
Что вы имеете в виду под детерминированностью? В чём практическое преимущество?
amhbr
В этом и дело, в MISRA C++ отсутствует категория Mandatory, большинство правил Required. Вы можете нарушить правило при достаточных основаниях. Если в конкретном проекте Required = Mandatory, то придется соблюдать.
Это один из вариантов, скорее характерный для С и, как отмечено, не самый удачный. В С++ есть более развитая система типов, концепты, пред/пост-условия, перегрузка, сокрытие реализации, т.о. можно написать код, чтобы функции вообще не вызывались с неправильными параметрами, это уберет часть "if-return". Естественно это не покрывает 100% случаев, но мы же не из тех кому "всё или ничего".
Имеется в виду предопределенность, просто этот термин используется реже, чем детерминированность. Определение: Детерминированность (от лат. determinans — «определяющий») — это свойство процесса или системы, при котором результат однозначно определяется начальными условиями, входными данными и правилами функционирования.
На практике это дает возможность делать большее количество оптимизаций, а главное уменьшает количество "что за ..." при чтении кода, если нужно могу привести примеры из библиотеки на шаблонах С++ для микроконтроллеров, там как раз улучшение читаемости за счет декларативности и однозначности, оптимальный машинный код, почти как рукописный ассемблер, операционная система времени компиляции.
Fedorkov
Я же вам процитировал кусок, в котором говорится, что хотя бы одно нарушение - это уже doesn't conform to MISRA.
Всё это как раз увеличивает количество "что за ...". В критичном к надёжности коде по умолчанию нужно использовать тупые как черенок лопаты решения, знакомые любому стажёру или индусу (на поддержку к которым с большой вероятностью перейдёт ваш код).
Siemargl
[[reproducible]], [[unsequenced]]
amhbr
Там имелось в виду автоматическое преобразование "нечистых" функций в "чистые", а для этого нужно во время компиляции уметь читать/писать метаданные и изменять код.
Siemargl
Ничего не понял. А автоматическое обрезание не желаете?
amhbr
Вы раввин?
vanxant
Ну если под if-ами мы проверяем результат захвата ресурсов (не обязательно памяти, мы можем например переводить какой-то конечный автомат из состояния x в y, а потом должны вернуть взад или перевести в z), то либо так, либо goto exit3.
Fedorkov
Самый типичный пример - это проверка аргументов на входе функции.
vanxant
я понимаю, но для проверки это вопрос вкуса, а для захвата ресурсов - паттерн, потенциально уменьшающий количество ошибок
Rub_paul
Жду, когда адепты единственного ретурна начнут требовать писать весь код в одну строчку. Ради повышения детерминированности :)
Rub_paul
Бумажная безопасность увы всегда важнее реальной. Менеджеру проще прикрыть задницу отчетом анализатора, чем разбирать реальные корки после аварии..