Pull to refresh

Comments 54

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

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

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

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

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

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

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

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

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

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

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

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

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

@trivially_relocatable_if_eligible
class Pencil {}

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

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

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

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

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

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

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

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

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

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

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

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

а почему вклчюение заголовчных файлов и файлов ресурсов должно семантически отличаться? я наоборт за #embed ну и включать ресурсы хочется не только в cpp но и в c тоже

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

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

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

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

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

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

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

Не нравится ни 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 {
...
}

Как мне написать структуру, которая для тривиального 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;
  ...
}

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

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

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

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

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

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

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

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

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

Да, пока что relocate будет использоваться только в контейнерах. При передаче параметров в функции ABI не позволяет воспользоваться relocate для стандартных типов. И нет способа сделать relocate для локальной переменной, чтобы компилятор для себя отметил, что деструктор тут не вызывать.

Можно было бы начать с того, чтобы добавить в контейнеры функции, которые вы-relocate-ивают элемент наружу:

  • optional::pluck() -> T

  • vector::pluck_back() -> T

И функции, которые вставляют элемент внутрь без move:

  • optional::emplace_func(std::invocable_r<T> auto)

  • vector::emplace_back_func(std::invocable_r<T> auto)

Тогда, используя optional, можно иметь подобие move-семантики Rust, только флаг живости объекта будет присутствовать в коде, а не трекаться автоматически.

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

UFO landed and left these words here

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

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

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

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

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

UFO landed and left these words here

Это неправда, так ведут себя объекты из стд, и то не все

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

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

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

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

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

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

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

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

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

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

Да фиг бы с ними - с компайл-тайм регулярками... 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 делает поиск в файле, который открывает пользователь.

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

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

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

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

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

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

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

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

Что за штука, не подскажете ?

Означает ли это, что у нас появится библиотека, которая делает unmarshal json как в Golang, только во-время компиляции напишет код, а не как в Golang в рантайме
А ещё, можно было бы protobuf какой-нибудь сериализовать без прекомпилятора.

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

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

ЯП улучшаются, а качество ПО ухудшается. Хотелось бы услышать мнения гуру: зачем нужны новшества, если конечный результат их использования будет заведомо плохим?

Не могу сказать за другие языки программирования, но качество C++ ПО ощутимо улучшается. Появились санитайзеры, выработалась культура по обязательному написанию тестов и использованию CI, компиляторы обзавелись внушительным количеством встроенной диагностики, меньше людей пишет в стиле "C++ с классами" (в институтах начали нормально рассказывать про RAII!), всё меньше проектов с самописными и забагованными версиями variant|string|optional. Да даже если судить по одним только Хабру и чатику pro.cxx, уровень квалификации очень ощутимо вырос за последние 15 лет.

У меня другие наблюдения. С одной стороны простой код действительно стал безопасней из коробки и поэтому простым девелоперам сосредоточенным на бизнес логике стало легче писать на плюсах. С другой стороны есть такая категория девелоперов которые явно умнее среднего, но не достаточно, они то как раз герои пословицы горе от ума. Поясню, они достаточно умны, чтобы написать нетривиальный системный/библиотечный код который полон УБ, но работает почти всегда правильно. Ума написать такой же, но без УБ уже не хватает. Это часто проблемы в многопоточном программирование, безопасности памяти, исключений.

Такие люди создают код который приносит невероятного масштаба жопу. Починить(с учётом уже всех существующих юз кейсов) очень сложно, переписать на стандартные/надёжные решения очень дорого, т.к. они отличаются архитектурно/по ограничениям.

Так вот последние нововведения в плюсы вообще никак не помогает таким людям писать более безопасным образом. Атомики прекрасный новый неисчерпаемый кладезь УБ. Понять модель памяти с++ не реально, можно только простить. Написать тесты на код с атомиками задача невероятной сложности. Да даже многопоточные тесты это 99,999% это тупо таймеры. Ни одна душенка в комитете даже не задумывается на эту тему - мы вам дали автомат, а технику безопасности сами как нибудь. Шаред поинтеры, до массового использования атомиков были проблемой номер 1, но атомики конечно победили с большим отрывом. Прекрасный дизайн шаред принтеров спустили в унитаз под названием ГЦ для бедных, только они не ГЦ и проблем создали намного больше чем решили. Теперь понять, что когда умирает в продуктовом коде решительно невозможно. Казалось бы в теории я и не должен, но в реальности ещё как должен.

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

То что делает комитет все нужно и действительно важно, но вы там почему то забываете писать много хороших примеров как пользоваться новыми игрушками, но ещё важнее написать как НЕ надо пользоваться ими. Где вашу мать техника безопасности на новые приблуды?

Задача на собеседовании - что выдаст на экран эта программа ;-)
Но это, конечно, обфускация и ненормативное программирование.

Sign up to leave a comment.

Information

Website
www.ya.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия