В середине февраля в Хагенберге состоялась встреча международного комитета по стандартизации языка программирования C++.



В этот раз прорабатывались следующие большие темы:
  • 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)


  1. YogMuskrat
    19.02.2025 09:43

    А что там по Safe C++, были разговоры? Или все, мертв?


    1. antoshkka Автор
      19.02.2025 09:43

      Safe C++ в своих примерах использует `#feature`, который является аналогом профилей. Так что комитет прежде всего сконцентрируется на профилях, при этом borrow checking (P3390 Safe C++) будет развиваться параллельно.

      Есть небольшой шанс увидеть Safe C++ в C++29, но работа предстоит титаническая


      1. tbl
        19.02.2025 09:43

        Тут пишут, что профили - это мертворожденный продукт, который будет допилен до конца приблизительно никогда https://izzys.casa/2024/11/on-safe-cxx/


        1. KanuTaH
          19.02.2025 09:43

          Там много чего пишут - еще про Трампа, курдов, токсичную маскулинность и так далее. Написано явно не совсем здоровым человеком, но увлекательно, спору нет - когда в свое время читал, просто-таки не мог оторваться от совершенно неожиданных поворотов сюжета.


  1. simplepersonru
    19.02.2025 09:43

    template <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;
    }


    1. antoshkka Автор
      19.02.2025 09:43

      С атрибутом будет длиннее на 4 символа т.к. `[[]]` :)

      Все старые правила по оптимизациям тривиальных типов в компиляторе остаются. Если нет особой нужны различать trivially_relocatable|nothrow movable|trivial, то в стандартную библиотеку в том же P2786 добавили std::relocate(from_begin, from_end, to), который для Point так же превратится в std::memmove.


      1. simplepersonru
        19.02.2025 09:43

        Спасибо, окей, по кишкам нет вопросов

        Возвращаюсь в область вкусовщины и синтаксиса :)

        1. Для этой фичи, которая кмк весьма опциональная, ввели аж ключевое слово. Я бы понял, если например механизмы шаред\юник птры обернули бы в ключевые слова и языковые конструкции (не библиотечные), чтобы где-то добавить им элегантности - это повсеместная история.

        2. Новое ключевое слово - еще больше усложняется разбор исходного кода C++, нужно поддержать всякие matcher-ы, разрабам компиляторов сильно больше работы (чем в сравнении с п.3)

        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 {
            // ...
        };


        1. antoshkka Автор
          19.02.2025 09:43

          Толкать метаинформацию, влияющую на ABI через атрибуты на практике получается неприятно. Комитету не понравилось как получилось с [[no_unique_address]], поэтому больше так делать не хотят. Контекстно зависимое ключевое слово в этом плане лучше - точно применится или будет ошибка компиляции, проще диагностировать неправильные использования.

          Ну а что касается вкусовщины - излишнее количество [[]] не идёт языку на пользу. Читается хуже и язык превращается из C++ в C[[]] %)


          1. simplepersonru
            19.02.2025 09:43

            Агап, про [[]] пишу "вынуждено", маловероятно что будут менять синтаксис атрибутов.

            Но если бы меняли, лично мне нравится Java вариант, достаточно элегантно

            @trivially_relocatable_if_eligible
            class Pencil {}

            Спасибо, чуть понятнее стало почему так (хоть и все равно не очень нравится концептуально по причинам из пунктов)


            1. antoshkka Автор
              19.02.2025 09:43

              Подобный вариант хотят сделать для пользовательских атрибутов, которые будут использоваться рефлексией. Но там пока даже особо не обсуждалась идея - сначала нужна основная часть рефлексии


              1. broly
                19.02.2025 09:43

                Кажется, что все прелести рефлексии C++ будут именно с пользовательскими атрибутами. Лучше бы сразу рефлексию реализовывали с их поддержкой. А то подозреваю, что начнут костыли делать через #embed, чтобы парсить самопальные атрибуты.


  1. simplepersonru
    19.02.2025 09:43

    #embed — ещё одна долгожданная новинка для C++26

    Почему как директива препроцессора, у C++ нет "курса" на отход от этого? Только чтобы с языком C было одинаково? Как будто constexpr (consteval) функция в данном случае было бы идеоматически правильнее.

    На этапе компиляции ведь файл там же где и был во время препроцессинга, если про .cpp речь, ну с хедерами не сканает так окей. Можно основной интерфейс сделать constexpr функцией c параметрами, макросом только нужную от препроцессора штуку передавать - относительный путь к файлу, в смысле разрулить абсолютный путь, чтобы у единицы трансляции был к нему доступ.


    1. hiewpoint
      19.02.2025 09:43

      Потому что добавить этот функционал в С++ автору предложения изначально не дали, он его отбэкпортил в Си, комитет по Си его принял, и теперь он именно в сишном виде таки доезжает до С++.

      Edit: полезная ссылка https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p1040r7.html


      1. slonopotamus
        19.02.2025 09:43

        добавить этот функционал в С++ автору предложения изначально не дали, он его отбэкпортил в Си, комитет по Си его принял, и теперь он именно в сишном виде таки доезжает до С++.

        А ловко он это придумал!) Не пустили через переднюю дверь, зашёл через боковую.


    1. antoshkka Автор
      19.02.2025 09:43

      На этапе компиляции ведь файл там же где и был во время препроцессинга

      Кажется что тогда сломается кеширование объектников по хешу препроцессирования - изменение ресурса не будет приводить к пересборке. А значит сломается ccache и куча аналогичных enterprise решений


  1. YogMuskrat
    19.02.2025 09:43

    А как соотносится Relocate с destructive move? Можно его в роли такового рассматривать? Или обращение к Relocate-нутому объекту будет старым добрым UB?


    1. Jijiki
      19.02.2025 09:43

      поидее суть в том что после релокации, гдето перед циклом, возможно, может быть вызов итератора на контейнер, и он будет уже текущим, а вот прям принципиально по вашему вопросу интересно,

      Скрытый текст
      ...
      relocate(from_begin, from_end, to);
      ...
      ::iterator<NameContainer> it = valueName.begin();
      ...
        cycle


    1. antoshkka Автор
      19.02.2025 09:43

      lifetime relocate-нутого объекта завершён. Его больше нет, так что обращение будет старым добрым UB.


      1. boldape
        19.02.2025 09:43

        Ничего не понятно про этот релокэйт, но очень интересно.

        auto x = std::make_unique<int>();
        auto y = std::unique_ptr<int>();
        std::relocate(&x, &x +1, &y);

        Вопрос, это вообще ок или это ещё один способ написать УБ? Если ок, то будет ли вызван деструктор для х? А что насчёт у был не пустым?


        1. antoshkka Автор
          19.02.2025 09:43

          Это UB, как если бы позвали

          auto x = std::make_unique<int>();
          std::destroy_at(&x);


  1. 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 {
    ...
    }


    1. 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;
        ...
      }


  1. m0xf
    19.02.2025 09:43

    Очень расстраивает, что быстро принимают сомнительные предложения, вроде trivially relocatable. При этом реально нужные вещи вроде рефлексии проходят очень медленно. А некоторые нужные предложения, вроде embed, вообще пришлось заносить через C23.


    1. antoshkka Автор
      19.02.2025 09:43

      Идее relocate больше 20 лет :) Во многих библиотеках подобный подход использовался и кастомизировался с помощью специфичных для библиотеки трейтов. Предложение по relocate просто стандартизирует способ подобного указания.

      Рефлексия движется крайне быстро. Я вообще удивлён что так молниеносно удалось 4 различные популярные реализации C++ фронтенда свести к общей части - рефлексии.


  1. Nemoumbra
    19.02.2025 09:43

    Бедный assume[[expression]] (since C++23)... Контракты полностью его функционал покрывают (а он покрывает компайл-тайм фичи контрактов).

    Вопрос - а у нас есть возможность в C++26 мувнуть куда-то локальный std::unique_ptr таким образом, чтобы компилятор понял, что вставлять вызов деструктора уже не надо? Ну или какой-нибудь другой класс, который либо владеет ресурсом, либо не владеет, скажем, std::any?


    1. YogMuskrat
      19.02.2025 09:43

      Блок про relocate как раз об этом, по идее.


      1. Nemoumbra
        19.02.2025 09:43

        И что, он вызовет мув-оператор копирования / мув-конструктор? Я именно про мув говорю.


        1. YogMuskrat
          19.02.2025 09:43

          Сам по себе мув же ничего не делает. Поведение будет зависеть от того, как именно его реализовать в классе - можно переприсвоить ресурсоы, а можно, как я понял, попробовать сделать релокацию. И тогда:

          После этого можно использовать std::trivially_relocate(from_begin, from_end, to) функцию, чтобы переместить объект, завершить время жизни (lifetime) старых объектов и начать время жизни новых объектов. На практике, функция будет перемещать объекты через std::memmove, полностью избегая вызовов конструкторов и деструкторов.


          1. Nemoumbra
            19.02.2025 09:43

            Так, ничего не понимаю. Если релокацию планируют писать внутри мувающего оператора присваивания, то откуда компилятор должен вытащить информацию, как реализован этот мув? А ему придётся это узнать, чтобы решить - вставлять ли на выходе из скоупа деструктор локальной переменной или нет. А вдруг std::relocate вызывается только в какой-то ветке, что тогда делать компилятору?


    1. Playa
      19.02.2025 09:43

      В стандартную библиотеку это не пролезет, пока не будет принято решение сломать ABI. А это будет ещё не скоро (а может и не будет, хотя по слухам MS всё-таки что-то замышляет).


  1. 9241304
    19.02.2025 09:43

    Неужели они наконец скопировали хоть одну полезную фичу из голанга))


  1. sic
    19.02.2025 09:43

    Наконец-то первый нетривиальный способ выстрелить другому в ногу. Это уже дуэль.


  1. Panzerschrek
    19.02.2025 09:43

    Нормальным перемещение в C++ будет только тогда, когда компилятор наконец то будет иметь возможность по крайней мере для локальных переменных и аргументов функций вызвать такое перемещение, которое не будет подразумевать последующего вызова деструктора для перемещаемой переменной. При этом должна быть ещё и проверка, что не происходит условного перемещения и нету обращения к перемещённым переменным.
    Что же касается текущего std::move - то его можно оставить для случаев, когда перемещается какой-то элемент структуры/массива или же элемент контейнера.


    1. antoshkka Автор
      19.02.2025 09:43

      Я тоже горю идеей destructive move, но со временем она кажется всё менее и менее привлекательной. Современные компиляторы очень неплохо убирают мертвый код деструкторов вида `~Foo() { if (not_moved) delete not_moved; }`. Современный LTO тоже весьма неплохо помогает компилятору в более сложных случаях move+destroy

      Если идею доработают до разумного состояния (последние предложения ломали приложения на ряде платформ и очень усложняли язык) - будет маленький праздник.


    1. ZirakZigil
      19.02.2025 09:43

      А если мой класс имеет какое-то уникальное состояние, сохраняющееся после мува, и влияющее на результат работы деструктора? Я вполне себе уверен, что такое свойство в общем случае нетривиально, а значит алгоритмически неразрешимо. А удалять код вида if (has_value) delete ..., как уже отметили, они и сейчас могут


  1. Panzerschrek
    19.02.2025 09:43

    Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации. Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++. Но почему-то стандартизация тянется на годы, если не на десятилетия. Почему так?

    При этом есть ряд фич, которые после принятия показали себя как нежизнеспособные. Например std::regex по сути бесполезен, т. к. стандарт не гарантирует, что он поддерживает UTF-8. Ну и ещё он жутко медленный. При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.


    1. antoshkka Автор
      19.02.2025 09:43

      Случай с embed показателен в плане медлительности и неэффективности комитета стандартизации.

      А на мой взгляд это успех - забраковали первые версии embed, которые ломали сборку и разработку множеству компаний; подсказали что правильнее реализовывать функционал через препроцессор и для лучшей совместимости лучше реализовать для C стандарта; полученный из комитета C #embed отревьюили опять, поправили проблемы, приняли в C++ и отправили в C изменения для синхронизации.

      Фича по сути своей почти что тривиальная, реализуется с перерывами на кофе и митинги за неделю в любом компиляторе C++.

      Количество комитов для поддержки embed в GCC перевалило уже за второй десяток, разработка и доработка идёт больше полугода. Кажется вы очень сильно недооцениваете проблему

      При этом его можно было бы грамотно реализовать, если поручить компиляцию регулярных выражений, известных во времени компиляции, самому компилятору C++ - чтобы они напрямую в машинный код трансформировались. До до этого комитет почему-то не дошёл.

      Вот только автор этого подхода и библиотеки ctre - как раз часть комитета C++ и она как раз втащила огромное количество constexpr улучшений, необходимых в дальнейшем как для рефлексии, так и для стандартизации ctre.


      1. Panzerschrek
        19.02.2025 09:43

        С "за одну неделю" я может и погорячился, но всё же embed не кажется чем-то, что уж настолько сложно, в сравнении с другими нововведениями.

        Про забраковали - можно поподробнее? Что там не так было? И не лучше ли было бы принять в стандарт не самую идеальную, но работающую версию, чтобы пользователи языка не были вынуждены несколько лет ждать?


      1. 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 делает поиск в файле, который открывает пользователь.


        1. antoshkka Автор
          19.02.2025 09:43

          С Boost.Regex остаётся проблема, что он позволяет backtracking, а соответсвенно уязвим к ReDOS атакам :(

          Рекомендую использовать Re2

          P.S.: у меня в команде хотели написать статью на эту тему. Было бы вам интересно почитать?


          1. Nemoumbra
            19.02.2025 09:43

            Во-первых, да, было бы!

            Во-вторых, не смог сходу найти статей про конкретно Boost.Regex от John Maddock, в которых бы демонстрировался ReDOS. Одну обзорную статью нашёл, в которой было много движков разобрано, там приводился конкретный пример, но я не смог воспроизвести атаку: ImHex отработал очень быстро.

            Вот все места, где у нас используется standalone Boost.Regex
            Вот все места, где у нас используется standalone Boost.Regex

            По-моему, если пользователь введёт дурной паттерн, то он имеет шанс самостоятельно закопаться, уж такова природа работы с неизвестными данными. Он с тем же успехом может не рассчитать, хватит ли у него оперативы для какой-нибудь другой тяжёлой операции, и прогореть. Если ReDOS страшнее, чем это, надо будет подумать.

            В-третьих, Re2 тащит за собой Abseil (кучу либ, дополнующий стандартную либу плюсов), т.е. наступает на грабли самого Буста.


  1. cdriper
    19.02.2025 09:43

    скрестили пальцы за рефлексию, все остальное это баловство...


  1. Dooez
    19.02.2025 09:43

    Очень интересно было бы послушать про placement new, launder и лайфтаймы с точки зрения стандарта.

    Казалось бы простая вещь: вызвать конструктор по адресу, но повсюду ждёт UB.