- PVSM.RU - https://www.pvsm.ru -

В этот раз прорабатывались следующие большие темы:
В стандарт C++26 приняли контейнер, пользующийся успехом в игровой индустрии и высокочастотной торговле, так же известный под именами 'bucket array' или 'object pool'.
Идея контейнера в следующем: контейнер владеет несколькими блоками памяти с элементами и хранит логический маркер для каждого элемента, который обозначает, активен ли этот элемент или удалён — так называемое поле пропуска (skipfield). Если элемент помечен как удалённый, он пропускается при итерации.
Когда все элементы в блоке удалены, блок удаляется, чтобы итерация не теряла производительность из-за необходимости пропускать пустые блоки. Если происходит вставка, когда все блоки заполнены, выделяется новый блок памяти.
Преимущества такой структуры заключаются в следующем:
При этом контейнер обладает некоторыми неожиданными особенностями:
O(1) получить доступ к N'ому элементу. Так что у контейнера нет operator[] и итераторы его не random-access, а bidirectional. Для доступа к N'ому элементу можно воспользоваться std::advance, который специализирован для контейнера и предоставляет сложность близкую к O(1).Больше информации о контейнере, со схемами и примерами, можно найти в предложении P0447 [1].
Рефлексия в C++ будет происходить на этапе компиляции, и есть все шансы увидеть её в стандарте C++26. В связи с этим комитет провёл большую работу по расширению возможностей программирования в constexpr:
constexpr. При этом std::hash остаётся не constexpr, так что для использования std::unordered_* контейнеров придётся предоставлять свою функцию хеширования.
Так же есть шансы что позволят использовать виртуальное наследование в constexpr в C++26, и constexpr корутины в C++29.
В направлении улучшения безопасности комитет движется семимильными шагами.
Гигантский шаг в сторону увеличения безопасности — это контракты, которые приняли в 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 [4]:
void handle_contract_violation(const std::contracts::contract_violation& violation) noexcept {
std::print("Contract {} violation. Trace:n{}", violation.comment(), std::stacktrace::current());
}
Больше деталей доступно в предложении P2900 [5]. Мои поздравления Тимуру Думлеру, участнику Рабочей Группы 21, и всем остальным авторам предложения по контрактам с принятием такого замечательного функционала в стандарт!
Так же, в C++26 приняли улучшение безопасности C++ через применение контрактов к стандартной библиотеке C++ (stadard library hardening). Другими словами, всё что мы делали вручную в примере с optional будет доступно «из коробки» для многих классов стандартной библиотеки. Подробности можно найти в P3471 [6]. Большинство стандартных библиотек как раз недавно обзавелись такими проверками, соответственно нововведение уже можно включить через специфичные макросы в старых стандартах C++, и скоро нововведение будет доступно с помощью стандартных контрактов.
optional<int>::operator* прекрасно отрабатывает на пустом optional и выдаёт мусорное значение, а санитайзеры не замечают проблему.
К другим механизмам для улучшения безопасности. С++ профили — возможность запрещать использовать некоторые конструкции языка и автоматически добавлять рантайм проверки. Такая замечательная идея, оказалась не достаточно проработанной чтобы оказаться в C++26. Но есть хорошая новость: решено было доработать и вынести идею в отдельный технический документ. Есть все шансы, что документ будет готов после завершения работы над C++26, но ещё до официальной публикации C++26 от ISO.
Помимо нового функционала для улучшения безопасности, идёт активная работа по уменьшению количества имеющихся Undefined Behavior в стандарте. В том числе, идёт работа над предложением от Рабочей группы 21 по стандартизации поведения placement new + reinterpret_cast (P3006 [7]). Предложение позволяет не использовать std::launder в случае если массив байт под placement new используется для создания объектов одного и того же типа, что делает поведение boost::optional, userver [8]::utils::FastPimpl, V8, Clickhouse строго определённым и при этом позволяет компилятору не пессимизировать производительность из-за std::launder. Тема весьма объёмная и крайне сложная, расскажем подробности на каком-нибудь хардкорном C++ мероприятии.
Разумеется, работа над рефлексией и безопасностью не останавливает C++ от улучшений производительности.
Когда в C++11 появилась move семантика, автор языка Бьёрн Страуструп рассказывал о нововведении на примере карандаша: «Вот у меня в левой руке карандаш. В реальном мире, чтобы переместить карандаш из левой руки в правую, мы его просто перекладываем… В C++ до move семантики, мы вынуждены были создавать в правой руке копию карандаша, и уничтожать карандаш в левой руке.»
Вот только и с C++11 с «карандашом» оставались неурядицы. После перемещения в левой руке у нас оставался «перемещённый в другое место пустой карандаш», для которого надо звать деструктор. В C++26 приняли давно ожидаемое разработчиками библиотек нововведение по релоцированию объектов. Предложение P2786 [9] позволяет размечать классы как «тривиально перемещаемые» с помощью добавления 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 — ещё одна долгожданная новинка для 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 [10]; некоторые конструкторы были сделаны explicit в P3430 [11]; добавлена возможность работать с комплексными числами в P2663 [12]; функция simd_split переименована в simd_chunk в P3441 [13]; функции избавлены от simd префиксов и суффиксов, вынесены в отдельный std::datapar неймспейс и импортированы в неймспейс std через using в P3287 [14].
Также ranges обзавелись новыми алгоритмами std::ranges::reserve_hint в P2846 [15] и std::views::to_input в P3137 [16].
На радость embedded разработчиков в P2976 [17] больше алгоритмов и генераторы псевдослучайных чисел разметили как freestanding (доступными в стандартной библиотеке работающей без поддержки операционной системы).
Наконец, в P3349 [18] разрешили реализациям стандартной библиотеки превращать contiguous итераторы в простые указатели для уменьшения размера генерируемого бинарного кода и уменьшения инстанцирований шаблонов.
C++26 всё ближе. Есть все шансы что в нём окажется ещё и рефлексия, но в нём точно не окажется pattern matching. Самое время приглядеться к черновику нового стандарта поближе, и если что-то работает не так как ожидается или что-то ломает вам разработку — пожалуйста напишите об этом в stdcpp.ru [4], ещё не поздно что-то исправить.
Кроме того, в скором времени состоятся:
Приходите, будет интересно!
Автор: antoshkka
Источник [21]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/411267
Ссылки в тексте:
[1] P0447: https://wg21.link/p0447
[2] P3372: https://wg21.link/P3372
[3] P3378: https://wg21.link/P3378
[4] Рабочей группы 21: https://stdcpp.ru/
[5] P2900: https://wg21.link/P2900
[6] P3471: https://wg21.link/P3471
[7] P3006: https://wg21.link/P3006
[8] userver: https://github.com/userver-framework/userver
[9] P2786: https://wg21.link/P2786
[10] P2933: https://wg21.link/P2933
[11] P3430: https://wg21.link/P3430
[12] P2663: https://wg21.link/P2663
[13] P3441: https://wg21.link/P3441
[14] P3287: https://wg21.link/P3287
[15] P2846: https://wg21.link/P2846
[16] P3137: https://wg21.link/P3137
[17] P2976: https://wg21.link/P2976
[18] P3349: https://wg21.link/P3349
[19] C++Russia: https://cppconf.ru/
[20] Встреча рабочей группы 21: https://dev.go.yandex/events/vstrecha-rg21-cpp
[21] Источник: https://habr.com/ru/companies/yandex/articles/882518/?utm_source=habrahabr&utm_medium=rss&utm_campaign=882518
Нажмите здесь для печати.