
В этот раз прорабатывались следующие большие темы:
- std::hive
- Constexpr, ещё больше constexpr
- Безопасность, контракты, hardening, профили, UB и std::launder
- Relocate
- #embed
std::hive
В стандарт C++26 приняли контейнер, пользующийся успехом в игровой индустрии и высокочастотной торговле, так же известный под именами 'bucket array' или 'object pool'.
Идея контейнера в следующем: контейнер владеет несколькими блоками памяти с элементами и хранит логический маркер для каждого элемента, который обозначает, активен ли этот элемент или удалён — так называемое поле пропуска (skipfield). Если элемент помечен как удалённый, он пропускается при итерации.
Когда все элементы в блоке удалены, блок удаляется, чтобы итерация не теряла производительность из-за необходимости пропускать пустые блоки. Если происходит вставка, когда все блоки заполнены, выделяется новый блок памяти.
Преимущества такой структуры заключаются в следующем:
- При удалении элементов не требуется перемещение оставшихся элементов.
- Вставки в полный контейнер также не вызывают перемещения объектов.
- Местоположения элементов в памяти остаются стабильными, и итераторы остаются действительными независимо от удаления или вставки.
- Контейнер может хранить не перемещаемые и не копируемые элементы.
При этом контейнер обладает некоторыми неожиданными особенностями:
- Контейнер неупорядоченный — вставка происходит в произвольное место контейнера.
- Есть методы для получения итератора из указателя на элемент внутри контейнера (что весьма полезно, доказано Boost.Intrusive).
- Из-за пропусков нет возможности за гарантированный
O(1)
получить доступ к N'ому элементу. Так что у контейнера нетoperator[]
и итераторы его не random-access, а bidirectional. Для доступа к N'ому элементу можно воспользоватьсяstd::advance
, который специализирован для контейнера и предоставляет сложность близкую кO(1)
.
Больше информации о контейнере, со схемами и примерами, можно найти в предложении P0447.
constexpr
Рефлексия в C++ будет происходить на этапе компиляции, и есть все шансы увидеть её в стандарте C++26. В связи с этим комитет провёл большую работу по расширению возможностей программирования в constexpr:
- Предложение P3372 разметило практически все контейнеры стандартной библиотеки как
constexpr
. При этом std::hash остаётся не constexpr, так что для использованияstd::unordered_*
контейнеров придётся предоставлять свою функцию хеширования. - Предложение P3378 разметило часть исключений стандартной библиотеки как constexpr.
Так же есть шансы что позволят использовать виртуальное наследование в
constexpr
в C++26, и constexpr корутины в C++29.Безопасность, контракты, libc++ hardening, профили, UB и std::launder
В направлении улучшения безопасности комитет движется семимильными шагами.
Гигантский шаг в сторону увеличения безопасности — это контракты, которые приняли в C++26 на этой встрече. Они позволяют выразить «контракт по использованию и состоянию функции или класса» в виде, понятном для компилятора. Давайте воспользуемся контрактами чтобы обезопасить
optional
:template <class T>
class optional {
public:
constexpr optional() noexcept
post( !has_value() )
;
const T& operator*() const
pre( has_value() )
;
template <class... Args>
constexpr void emplace(Args&&... args)
post( has_value() )
;
constexpr void reset()
post( !has_value() )
;
constexpr bool has_value() const noexcept;
// ...
};
Условия, которые пишутся в
pre
, post
и contract_assert
называются предикатом контракта. Предикаты не должны менять состояние класса или параметров, не должны иметь побочных эффектов и не должны выкидывать исключений.Указав через опции компилятора, что делать в случае нарушения предиката контракта, можно получить следующие варианты поведения:
-
enforce
— в случае нарушения контракта будет вызвана функция handle_contract_violation и приложение завершит свою работу. Такой режим удобен, например, для отладочных сборок — в случае ошибки (нарушения контракта) будет выдана диагностика и приложение завершится, не давая возможность проигнорировать проблему. -
observe
— в случае нарушения контракта будет вызвана функция handle_contract_violation, и если управление будет возвращено из неё, то программа продолжит работу как ни в чём не бывало. Такой режим хорош для тестинга/pre-stable, где ошибки хочется логировать и реагировать на них, но не критичное для системы приложение может продолжать работу. -
quick-enforce
— при нарушении контракта приложение будет мгновенно завершено. Полезно для прода, если сервис работает с чувствительными данными и лучше приложение перезапустить, чем позволить ошибке влиять на дальнейшую логику. -
ignore
— контракт не проверяется и ничего не зовётся. Так же полезно для продакшн решений в закрытом контуре, крайне требовательных к производительности.
У C++26 контрактов есть пара интересных особенностей. Во первых, если компилятор обнаруживает нарушение контракта на этапе компиляции, то сборка останавливается и выдаётся диагностика. Во вторых, предикат контракта может и не вычисляться. Вместо этого его значение будет найдено с помощью
optional
:optional<MyType> value;
value.emplace(42, 3.141593); // Можно не считать предикат `post` контракта, если
// компилятор и так понял что optional не пустой
value->Initialize(); // Можно не считать предикат `pre` контракта,
// зная что optional гарантированно не пустой
Функцию handle_contract_violation можно переопределять в коде и она отлично работает с принятым в C++23
std::stacktrace
предложением от Рабочей группы 21:void handle_contract_violation(const std::contracts::contract_violation& violation) noexcept {
std::print("Contract {} violation. Trace:\n{}", violation.comment(), std::stacktrace::current());
}
Больше деталей доступно в предложении P2900. Мои поздравления Тимуру Думлеру, участнику Рабочей Группы 21, и всем остальным авторам предложения по контрактам с принятием такого замечательного функционала в стандарт!
Так же, в C++26 приняли улучшение безопасности C++ через применение контрактов к стандартной библиотеке C++ (stadard library hardening). Другими словами, всё что мы делали вручную в примере с
optional
будет доступно «из коробки» для многих классов стандартной библиотеки. Подробности можно найти в P3471. Большинство стандартных библиотек как раз недавно обзавелись такими проверками, соответственно нововведение уже можно включить через специфичные макросы в старых стандартах C++, и скоро нововведение будет доступно с помощью стандартных контрактов.optional<int>::operator*
прекрасно отрабатывает на пустом optional и выдаёт мусорное значение, а санитайзеры не замечают проблему.К другим механизмам для улучшения безопасности. С++ профили — возможность запрещать использовать некоторые конструкции языка и автоматически добавлять рантайм проверки. Такая замечательная идея, оказалась не достаточно проработанной чтобы оказаться в C++26. Но есть хорошая новость: решено было доработать и вынести идею в отдельный технический документ. Есть все шансы, что документ будет готов после завершения работы над C++26, но ещё до официальной публикации C++26 от ISO.
Помимо нового функционала для улучшения безопасности, идёт активная работа по уменьшению количества имеющихся Undefined Behavior в стандарте. В том числе, идёт работа над предложением от Рабочей группы 21 по стандартизации поведения
placement new + reinterpret_cast
(P3006). Предложение позволяет не использовать std::launder
в случае если массив байт под placement new используется для создания объектов одного и того же типа, что делает поведение boost::optional
, userver::utils::FastPimpl, V8, Clickhouse строго определённым и при этом позволяет компилятору не пессимизировать производительность из-за std::launder
. Тема весьма объёмная и крайне сложная, расскажем подробности на каком-нибудь хардкорном C++ мероприятии.Relocate
Разумеется, работа над рефлексией и безопасностью не останавливает C++ от улучшений производительности.
Когда в C++11 появилась move семантика, автор языка Бьёрн Страуструп рассказывал о нововведении на примере карандаша: «Вот у меня в левой руке карандаш. В реальном мире, чтобы переместить карандаш из левой руки в правую, мы его просто перекладываем… В C++ до move семантики, мы вынуждены были создавать в правой руке копию карандаша, и уничтожать карандаш в левой руке.»
Вот только и с C++11 с «карандашом» оставались неурядицы. После перемещения в левой руке у нас оставался «перемещённый в другое место пустой карандаш», для которого надо звать деструктор. В C++26 приняли давно ожидаемое разработчиками библиотек нововведение по релоцированию объектов. Предложение P2786 позволяет размечать классы как «тривиально перемещаемые» с помощью добавления
trivially_relocatable_if_eligible
после имени класса:template <class T>
class Pencil trivially_relocatable_if_eligible {
// ...
};
После этого можно использовать
std::trivially_relocate(from_begin, from_end, to)
функцию, чтобы переместить объект, завершить время жизни (lifetime) старых объектов и начать время жизни новых объектов. На практике, функция будет перемещать объекты через std::memmove
, полностью избегая вызовов конструкторов и деструкторов.Инструмент весьма специфичный, ограничения и подробности расписаны в предложении.
std::string
или std::list
), то он не тривиально релоцируемый.Вторая проблема — бинарный прогамный интерфейс (ABI). На некоторых платформах есть возможность передавать подобные классы в функции более эффективно (через регистры), но при этом меняется ассемблер для работы с ними. Соответственно, чтобы была возможность работать быстрее, но весь существующий собранный код не разломался — потребовался отдельный «маркер» для класса.
#embed
#embed
— ещё одна долгожданная новинка для C++26, позволяющая содержимое файла «зашить» в бинарник. Вот небольшой синтетический пример, на котором можно познакомиться со всеми параметрами этой новой препроцессорной директивы:constexpr unsigned char whl[] = {
#embed "ches.glsl" \
prefix(0xEF, 0xBB, 0xBF, ) /* префикс для вставки, если ресурс не пустой */ \
suffix(,) /* суффикс для вставки, если ресурс не пустой */ \
if_empty(0xBB, 0xBF, ) /* что вставить, если ресурс пустой */ \
limit(1024*1024) /* максимальный размер для вставки */ \
0
};
Как ни странно, это нововведение привело к ускорению компиляции. Так, например, разработчики GCC соптимизировали инициализацию больших массивов, что привело к ускорению компиляции на некоторых случаях с минут до секунд, в том числе и на старых кодовых базах.
Мы в проекте ? userver тоже рады будем воспользоваться подобным механизмом, например, для встраивания SQL запросов из файлов в код и для встраивания админских/диагностических веб-страниц в сервер.
Прочие новинки
На встрече шла активная работа по улучшению
std::simd
. Были добавлены новые перегрузки и функции для работы с битами в P2933; некоторые конструкторы были сделаны explicit в P3430; добавлена возможность работать с комплексными числами в P2663; функция simd_split
переименована в simd_chunk
в P3441; функции избавлены от simd префиксов и суффиксов, вынесены в отдельный std::datapar
неймспейс и импортированы в неймспейс std
через using
в P3287.Также ranges обзавелись новыми алгоритмами
std::ranges::reserve_hint
в P2846 и std::views::to_input
в P3137.На радость embedded разработчиков в P2976 больше алгоритмов и генераторы псевдослучайных чисел разметили как freestanding (доступными в стандартной библиотеке работающей без поддержки операционной системы).
Наконец, в P3349 разрешили реализациям стандартной библиотеки превращать contiguous итераторы в простые указатели для уменьшения размера генерируемого бинарного кода и уменьшения инстанцирований шаблонов.
Итоги
C++26 всё ближе. Есть все шансы что в нём окажется ещё и рефлексия, но в нём точно не окажется pattern matching. Самое время приглядеться к черновику нового стандарта поближе, и если что-то работает не так как ожидается или что-то ломает вам разработку — пожалуйста напишите об этом в stdcpp.ru, ещё не поздно что-то исправить.
Кроме того, в скором времени состоятся:
- C++Russia 13, 20-21 марта — будут доклады по C++, стандартизации, ? userver и многое другое
- Встреча рабочей группы 21 25 марта — расскажем о C++, ответим на вопросы. Высок шанс, что к нам сможет присоединиться (онлайн или офлайн) Тимур Думлер и из первых рук рассказать о контрактах
Приходите, будет интересно!
Комментарии (43)
simplepersonru
19.02.2025 09:43template <class T> class Pencil trivially_relocatable_if_eligible { // ... };
Честно, вызывает смущение синтаксис trivially_relocatable_if_eligible, ИМХО лучше бы это был атрибут =) Тем более такой длинный. А если потом захочется еще какое-нибудь знание протащить в класс (мета-информацию), дальше в строчку пихать или как. clang-format наверное одуреет с этого =). С учетом что щас нет пользовательских атрибутов (кроме разных самописных плагинов к компилятору), почему бы и нет, никого не обидит резервированный атрибут
Ну и очередная штука которую по идее хочется пихать почти везде, как всякие noexcept, const, explicit, компилятор сам не будет принимать решения о trivially_relocatable? Даже для какого-нибудь Point ?
struct Point { double x = 0; double y = 0; }
antoshkka Автор
19.02.2025 09:43С атрибутом будет длиннее на 4 символа т.к. `[[]]` :)
Все старые правила по оптимизациям тривиальных типов в компиляторе остаются. Если нет особой нужны различать trivially_relocatable|nothrow movable|trivial, то в стандартную библиотеку в том же P2786 добавили
std::relocate(from_begin, from_end, to)
, который для Point так же превратится вstd::memmove
.simplepersonru
19.02.2025 09:43Спасибо, окей, по кишкам нет вопросов
Возвращаюсь в область вкусовщины и синтаксиса :)
Для этой фичи, которая кмк весьма опциональная, ввели аж ключевое слово. Я бы понял, если например механизмы шаред\юник птры обернули бы в ключевые слова и языковые конструкции (не библиотечные), чтобы где-то добавить им элегантности - это повсеместная история.
Новое ключевое слово - еще больше усложняется разбор исходного кода C++, нужно поддержать всякие matcher-ы, разрабам компиляторов сильно больше работы (чем в сравнении с п.3)
Это при том что в синтаксисе уже есть работающий механизм толкать мета-информацию к символам - атрибуты. Зачем вводить вводить новый механизм, если можно использовать старый
С атрибутом будет длиннее на 4 символа т.к. [[]]
4.Я имел ввиду что это могло бы выглядеть примерно так, как подобные вещи решаются в Java\C#\Rust\... почти любой современный язык на самом деле, про динамические и говорить не нужно:
template <class T> [[trivially_relocatable_if_eligible]] class Pencil { // ... };
Давайте попробуем масштабировать количество атрибутов, допустим еще +2 опциональной мета-информации с большим именем появится в С++29 или еще где, неважно. Получается на любую такую фичу нужно вводить новое ключевое слово? Или только выборочно, значит какие-то вещи через атрибуты, какие-то нет
// OK template <class T> [[trivially_relocatable_if_eligible]] [[serializable_if_trivial]] [[hello_from_stdcpp_mazafaka]] class Pencil { // ... }; // Сомнительно template <class T> class Pencil trivially_relocatable_if_eligible serializable_if_trivial hello_from_stdcpp_mazafaka { // ... }; // Сомнительно, но окей (вкусовщина, но считаю хуже первого варианта) // + завал мусорных ключевых слов, // которые не должны быть ключевыми словами (имхо) template <class T> class Pencil trivially_relocatable_if_eligible serializable_if_trivial hello_from_stdcpp_mazafaka { // ... };
antoshkka Автор
19.02.2025 09:43Толкать метаинформацию, влияющую на ABI через атрибуты на практике получается неприятно. Комитету не понравилось как получилось с [[no_unique_address]], поэтому больше так делать не хотят. Контекстно зависимое ключевое слово в этом плане лучше - точно применится или будет ошибка компиляции, проще диагностировать неправильные использования.
Ну а что касается вкусовщины - излишнее количество [[]] не идёт языку на пользу. Читается хуже и язык превращается из C++ в C[[]] %)
simplepersonru
19.02.2025 09:43Агап, про [[]] пишу "вынуждено", маловероятно что будут менять синтаксис атрибутов.
Но если бы меняли, лично мне нравится Java вариант, достаточно элегантно
@trivially_relocatable_if_eligible class Pencil {}
Спасибо, чуть понятнее стало почему так (хоть и все равно не очень нравится концептуально по причинам из пунктов)
antoshkka Автор
19.02.2025 09:43Подобный вариант хотят сделать для пользовательских атрибутов, которые будут использоваться рефлексией. Но там пока даже особо не обсуждалась идея - сначала нужна основная часть рефлексии
broly
19.02.2025 09:43Кажется, что все прелести рефлексии C++ будут именно с пользовательскими атрибутами. Лучше бы сразу рефлексию реализовывали с их поддержкой. А то подозреваю, что начнут костыли делать через #embed, чтобы парсить самопальные атрибуты.
simplepersonru
19.02.2025 09:43#embed — ещё одна долгожданная новинка для C++26
Почему как директива препроцессора, у C++ нет "курса" на отход от этого? Только чтобы с языком C было одинаково? Как будто constexpr (consteval) функция в данном случае было бы идеоматически правильнее.
На этапе компиляции ведь файл там же где и был во время препроцессинга, если про .cpp речь, ну с хедерами не сканает так окей. Можно основной интерфейс сделать constexpr функцией c параметрами, макросом только нужную от препроцессора штуку передавать - относительный путь к файлу, в смысле разрулить абсолютный путь, чтобы у единицы трансляции был к нему доступ.
hiewpoint
19.02.2025 09:43Потому что добавить этот функционал в С++ автору предложения изначально не дали, он его отбэкпортил в Си, комитет по Си его принял, и теперь он именно в сишном виде таки доезжает до С++.
Edit: полезная ссылка https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1040r7.html
slonopotamus
19.02.2025 09:43добавить этот функционал в С++ автору предложения изначально не дали, он его отбэкпортил в Си, комитет по Си его принял, и теперь он именно в сишном виде таки доезжает до С++.
А ловко он это придумал!) Не пустили через переднюю дверь, зашёл через боковую.
antoshkka Автор
19.02.2025 09:43На этапе компиляции ведь файл там же где и был во время препроцессинга
Кажется что тогда сломается кеширование объектников по хешу препроцессирования - изменение ресурса не будет приводить к пересборке. А значит сломается ccache и куча аналогичных enterprise решений
YogMuskrat
19.02.2025 09:43А как соотносится Relocate с destructive move? Можно его в роли такового рассматривать? Или обращение к Relocate-нутому объекту будет старым добрым UB?
Jijiki
19.02.2025 09:43поидее суть в том что после релокации, гдето перед циклом, возможно, может быть вызов итератора на контейнер, и он будет уже текущим, а вот прям принципиально по вашему вопросу интересно,
Скрытый текст
... relocate(from_begin, from_end, to); ... ::iterator<NameContainer> it = valueName.begin(); ... cycle
antoshkka Автор
19.02.2025 09:43lifetime relocate-нутого объекта завершён. Его больше нет, так что обращение будет старым добрым UB.
boldape
19.02.2025 09:43Ничего не понятно про этот релокэйт, но очень интересно.
auto x = std::make_unique<int>(); auto y = std::unique_ptr<int>(); std::relocate(&x, &x +1, &y);
Вопрос, это вообще ок или это ещё один способ написать УБ? Если ок, то будет ли вызван деструктор для х? А что насчёт у был не пустым?
antoshkka Автор
19.02.2025 09:43Это UB, как если бы позвали
auto x = std::make_unique<int>(); std::destroy_at(&x);
Kelbon
19.02.2025 09:43Не нравится ни trivial relocation через новое ключевое слово, ни контракты. Как мне написать структуру, которая для тривиального T тривиальная, а для нетривиального - нет? Да никак. Потому что это не специализация внешней std::trivial_relocatable<T>, а ключевое слово. И опшнлы в стандартной библиотеке никогда не получат этого нововведения получается и т.д.
Насчёт контрактов, синтаксис - сомнительный, изменение поведения - ещё сомнительнее. Нет по дефолту поведения UB на контракте. Все будут ставить observe и писать unreachable.
Более того, я уже вижу как их будут использовать:
#define LOGME pre(false) void handle_contract_violation() { print(std::stacktrace::current().front().name()); } void foo(int i) LOGME { ... }
antoshkka Автор
19.02.2025 09:43Как мне написать структуру, которая для тривиального T тривиальная, а для нетривиального - нет
В имени аргумента if_eligible не просто так. Компилятор проверит что все члены класса тоже trivially_relocatable, и только тогда трейт будет возвращать что класс действительно тривиально релоцируемый. Например:
template <class T> class optional trivially_relocatable_if_eligible { union { Empty empty{}; T payload; } data_; // ... };
Приведённый optional будет trivially_relocatable если ABI платформы разрешают делать trivially_relocatable юнионы и T является trivially_relocatable.
Нет по дефолту поведения UB на контракте
Этот режим "вынули" из C++26. Дальше в планах его добавить, чтобы компилятор мог оптимизировать из расчёта на то, что контракт никогда не нарушается.
Более того, я уже вижу как их будут использовать:
Ну да, можно многими конструкциям во многих языках программирования пользоваться заведомо неправильно. В случае контрактов это будет как минимум не удобно, ведь проще сделать:
#define LOGME std::print(std::stacktrace::current().front().name()) void foo(int i) { LOGME; ... }
m0xf
19.02.2025 09:43Очень расстраивает, что быстро принимают сомнительные предложения, вроде trivially relocatable. При этом реально нужные вещи вроде рефлексии проходят очень медленно. А некоторые нужные предложения, вроде embed, вообще пришлось заносить через C23.
antoshkka Автор
19.02.2025 09:43Идее relocate больше 20 лет :) Во многих библиотеках подобный подход использовался и кастомизировался с помощью специфичных для библиотеки трейтов. Предложение по relocate просто стандартизирует способ подобного указания.
Рефлексия движется крайне быстро. Я вообще удивлён что так молниеносно удалось 4 различные популярные реализации C++ фронтенда свести к общей части - рефлексии.
Nemoumbra
19.02.2025 09:43Бедный
assume[[expression]] (since C++23)
... Контракты полностью его функционал покрывают (а он покрывает компайл-тайм фичи контрактов).Вопрос - а у нас есть возможность в C++26 мувнуть куда-то локальный
std::unique_ptr
таким образом, чтобы компилятор понял, что вставлять вызов деструктора уже не надо? Ну или какой-нибудь другой класс, который либо владеет ресурсом, либо не владеет, скажем,std::any
?YogMuskrat
19.02.2025 09:43Блок про
relocate
как раз об этом, по идее.Nemoumbra
19.02.2025 09:43И что, он вызовет мув-оператор копирования / мув-конструктор? Я именно про мув говорю.
YogMuskrat
19.02.2025 09:43Сам по себе мув же ничего не делает. Поведение будет зависеть от того, как именно его реализовать в классе - можно переприсвоить ресурсоы, а можно, как я понял, попробовать сделать релокацию. И тогда:
После этого можно использовать std::trivially_relocate(from_begin, from_end, to) функцию, чтобы переместить объект, завершить время жизни (lifetime) старых объектов и начать время жизни новых объектов. На практике, функция будет перемещать объекты через std::memmove, полностью избегая вызовов конструкторов и деструкторов.
Nemoumbra
19.02.2025 09:43Так, ничего не понимаю. Если релокацию планируют писать внутри мувающего оператора присваивания, то откуда компилятор должен вытащить информацию, как реализован этот мув? А ему придётся это узнать, чтобы решить - вставлять ли на выходе из скоупа деструктор локальной переменной или нет. А вдруг std::relocate вызывается только в какой-то ветке, что тогда делать компилятору?
Playa
19.02.2025 09:43В стандартную библиотеку это не пролезет, пока не будет принято решение сломать ABI. А это будет ещё не скоро (а может и не будет, хотя по слухам MS всё-таки что-то замышляет).
Panzerschrek
19.02.2025 09:43Нормальным перемещение в C++ будет только тогда, когда компилятор наконец то будет иметь возможность по крайней мере для локальных переменных и аргументов функций вызвать такое перемещение, которое не будет подразумевать последующего вызова деструктора для перемещаемой переменной. При этом должна быть ещё и проверка, что не происходит условного перемещения и нету обращения к перемещённым переменным.
Что же касается текущего std::move - то его можно оставить для случаев, когда перемещается какой-то элемент структуры/массива или же элемент контейнера.antoshkka Автор
19.02.2025 09:43Я тоже горю идеей destructive move, но со временем она кажется всё менее и менее привлекательной. Современные компиляторы очень неплохо убирают мертвый код деструкторов вида `~Foo() { if (not_moved) delete not_moved; }`. Современный LTO тоже весьма неплохо помогает компилятору в более сложных случаях move+destroy
Если идею доработают до разумного состояния (последние предложения ломали приложения на ряде платформ и очень усложняли язык) - будет маленький праздник.
ZirakZigil
19.02.2025 09:43А если мой класс имеет какое-то уникальное состояние, сохраняющееся после мува, и влияющее на результат работы деструктора? Я вполне себе уверен, что такое свойство в общем случае нетривиально, а значит алгоритмически неразрешимо. А удалять код вида if (has_value) delete ..., как уже отметили, они и сейчас могут
Panzerschrek
19.02.2025 09:43Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации. Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++. Но почему-то стандартизация тянется на годы, если не на десятилетия. Почему так?
При этом есть ряд фич, которые после принятия показали себя как нежизнеспособные. Например std::regex по сути бесполезен, т. к. стандарт не гарантирует, что он поддерживает UTF-8. Ну и ещё он жутко медленный. При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.antoshkka Автор
19.02.2025 09:43Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации.
А на мой взгляд это успех - забраковали первые версии embed, которые ломали сборку и разработку множеству компаний; подсказали что правильнее реализовывать функционал через препроцессор и для лучшей совместимости лучше реализовать для C стандарта; полученный из комитета C #embed отревьюили опять, поправили проблемы, приняли в C++ и отправили в C изменения для синхронизации.
Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++.
Количество комитов для поддержки embed в GCC перевалило уже за второй десяток, разработка и доработка идёт больше полугода. Кажется вы очень сильно недооцениваете проблему
При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.
Вот только автор этого подхода и библиотеки ctre - как раз часть комитета C++ и она как раз втащила огромное количество constexpr улучшений, необходимых в дальнейшем как для рефлексии, так и для стандартизации ctre.
Panzerschrek
19.02.2025 09:43С "за одну неделю" я может и погорячился, но всё же embed не кажется чем-то, что уж настолько сложно, в сравнении с другими нововведениями.
Про забраковали - можно поподробнее? Что там не так было? И не лучше ли было бы принять в стандарт не самую идеальную, но работающую версию, чтобы пользователи языка не были вынуждены несколько лет ждать?
Nemoumbra
19.02.2025 09:43Да фиг бы с ними - с компайл-тайм регулярками... std::regex тупо небезопасен - мэтчинг по конечному автомату реализован рекурсивно, а не итеративно, поэтому на некоторых входах оно просто сегфолтит из-за stackoverflow, роняя всё приложение. Этот баг был замечен в gcc ещё в 2010х годах, но его не починили, как я понял, из-за ABI. Итеративный regex есть в boost, но... Процитирую автора ImHex:
Oh yeah, boost regex depends on:
assert
concept_check
config
container_hash
core
integer
mpl
predef
smart_ptr
static_assert
throw_exception
type_traits
Нет, бывает и больше зависимостей...
Впрочем, какие-то энтузиасты смогли переписать бустовый regex, чтобы он полагался только на стандартную либу, так что мы перешли на эту standalone версию. Вариант "не пихать туда непроверенный ввод" не годится, т.к. ImHex делает поиск в файле, который открывает пользователь.
antoshkka Автор
19.02.2025 09:43С Boost.Regex остаётся проблема, что он позволяет backtracking, а соответсвенно уязвим к ReDOS атакам :(
Рекомендую использовать Re2
P.S.: у меня в команде хотели написать статью на эту тему. Было бы вам интересно почитать?
Nemoumbra
19.02.2025 09:43Во-первых, да, было бы!
Во-вторых, не смог сходу найти статей про конкретно
Boost.Regex
от John Maddock, в которых бы демонстрировался ReDOS. Одну обзорную статью нашёл, в которой было много движков разобрано, там приводился конкретный пример, но я не смог воспроизвести атаку: ImHex отработал очень быстро.Вот все места, где у нас используется standalone Boost.Regex По-моему, если пользователь введёт дурной паттерн, то он имеет шанс самостоятельно закопаться, уж такова природа работы с неизвестными данными. Он с тем же успехом может не рассчитать, хватит ли у него оперативы для какой-нибудь другой тяжёлой операции, и прогореть. Если ReDOS страшнее, чем это, надо будет подумать.
В-третьих, Re2 тащит за собой
Abseil
(кучу либ, дополнующий стандартную либу плюсов), т.е. наступает на грабли самого Буста.
Dooez
19.02.2025 09:43Очень интересно было бы послушать про placement new, launder и лайфтаймы с точки зрения стандарта.
Казалось бы простая вещь: вызвать конструктор по адресу, но повсюду ждёт UB.
YogMuskrat
А что там по Safe C++, были разговоры? Или все, мертв?
antoshkka Автор
Safe C++ в своих примерах использует `#feature`, который является аналогом профилей. Так что комитет прежде всего сконцентрируется на профилях, при этом borrow checking (P3390 Safe C++) будет развиваться параллельно.
Есть небольшой шанс увидеть Safe C++ в C++29, но работа предстоит титаническая
tbl
Тут пишут, что профили - это мертворожденный продукт, который будет допилен до конца приблизительно никогда https://izzys.casa/2024/11/on-safe-cxx/
KanuTaH
Там много чего пишут - еще про Трампа, курдов, токсичную маскулинность и так далее. Написано явно не совсем здоровым человеком, но увлекательно, спору нет - когда в свое время читал, просто-таки не мог оторваться от совершенно неожиданных поворотов сюжета.