И даже смешно, что критик не осиливает 1 строчку кода без ошибок написать
Про какую именно ошибку речь? Про то, что нет string_view<char>? Ну так в комментариях никто не обязан сходу писать компилирующийся и линкующийся код. Можно считать, что там написано либо:
Когда вектора -- это еще неплохо, бывает что и векторов нет, а люди бабахаются с голыми new, а то и malloc-ами. Только вот это же про говнокод, а не про язык. В языке-то давно средства есть, и рекомендации по их использованию.
Откуда возьмутся такие ошибки в С++, если на нём писать как на С++, а не как на С?
Да хотя бы из-за невнимательности программиста. Сохранить где-то ссылку на временную переменную, которая протухает при выходе из скоупа -- как два байта. Вы запросто это можете получить даже на обычном std::max.
Распространённый совет — предпочитать std::vector обычным C-массивам и статическим векторам
Блин, да вы бы читать научились, раз уж приводите цитату из GSL. Там же прямое противоречие вашему утверждению про "распространенный совет -- предпочитать std::vector ... и статическим векторам".
For a fixed-length array, use std::array, which does not degenerate to a pointer when passed to a function and does know its size. Also, like a built-in array, a stack-allocated std::array keeps its elements on the stack.
Совет предпочитать использовать std::vector С-шным массивам, действительно, очень давний. ЕМНИП, Страуструп дает его начиная с 3-го издания своей книги "Язык программирования C++", которое вышло уже после появления и стандартизации STL. Но, во-первых, относился он в большей степени к массивам, которые меняют свой размер в динамине. И, во-вторых, с тех пор уже поколение выросло, а в STL уже почти полтора десятка лет есть std::array. Надо бы делать проправки на современные реалии.
Если говорить о технической составляющей, то применение std::launder для заявленных вами целей выглядит избыточным и в C++17. std::launder предназначен для других целей, следовательно, не нужен. А если мы полагаемся на то, что пока имеющиеся компиляторы не эксплуатируют данный UB, то достаточно простого reinterpret_cast-а.
И если уж и писать статью, то про какой-то условный:
template<typename T>
[[nodiscard]]
T *
my_start_lifetime_as(void * raw_ptr) noexcept {
#if defined(__cpp_lib_start_lifetime_as)
// При наличии языковых средств действуем по фен-шую.
return std::start_lifetime_as<T>(raw_ptr);
#else
// Скрещиваем пальцы и надеемся, что компиляторщики пока еще
// в своем уме (но это не точно).
return reinterpret_cast<T *>(raw_ptr);
#endif
}
Простите за прямоту, но этого в статье нет от слова совсем.
И не стоит меня обвинять за то, что кто-то применит этот материал не по назначению
Стоит, поскольку в статье нет достаточного количества предупреждений о том, чем это может потенциально грозить. Как и нет рекомендаций о том, чем следует заменить этот хак опираясь на свежие стандарты.
Но если речь о реальности C++17 — такие подходы остаются рабочими
Формально они-то как раз и не рабочие. И никто не даст вам гарантии, что условный clang-25, который начнет эксплуатировать данный UB, оставит старое поведение для C++17. Т.е. если кому-то взбредет в голову использовать описанный вами подход в своем коде, то это будет похоже на закладку мины замедленного действия. Работает, работает, о том, как оно сделано, уже никто и не знает, т.к. автор ушел в закат пару лет назад. В один прекрасный момент случается обновление компилятора и разбирайся потом почему перестало работать то, что работало.
Так что не вводите в заблуждение читателей. То, что вы описываете -- это хак, который пока что работает, т.к. до C++17 не было std::launder и компиляторщики не позволяли себе эксплуатировать этот UB. После C++20 и C++23 руки у них развязаны. И хак этот не имеет отношения к " гибким инструментам управления памятью и типобезопасностью".
Важно: мы не создаём объект в строгом смысле. Это UB по стандарту, если объект не был создан явно через placement new или иной способом, который инициализирует объект. Но если наш тип POD (Plain Old Data), то такое поведение часто работает в реальных реализациях.
Вместо часто работает должно было бы быть пока еще работает
После добавления в язык std::start_lifetime_as у компиляторщиков развязаны руки для того, чтобы начать эксплуатировать UB, на котором построен ваш код.
Не сделать forward decl с оригинальным классом, чтобы использовать этот юзинг (т.к. он как правило рядом с определением класса)
Бесконтрольные forward decl, к сожалению, прямой путь к хоть и мелкому, но геморрою. Такие определения следует держать в одном месте, а тогда и using-и не проблема от слова совсем.
Для меня выглядит как исключительное событие
Редкое, но не то, чтобы исключительное. Например, для unique_ptr -- был простой, стал unique_ptr с кастомным deleter-ом. Для shared_ptr -- был обычный std::shared_ptr, поменяли на кастомный без поддержки weak-references и с простым счетчиком ссылок место атомарного (по типу Rc в Rust, который отличается от Arc). Или же был обычный std::shared_ptr, а стал каким-нибудь boost::intrusive_ptr.
using Int = int;
А давайте сделаем вашу попытку пошутить более серьезной и возьмем такой пример:
using AxisX = int;
using AxisY = int;
using Width = int;
using Height = int;
using Radius = int;
...
ShapeUptr makeRectangle(AxisX x, AxisY y, Width cx, Height cx);
ShapeUptr makeCircle(AxisX x, AxisY y, Radius r);
Очевиднее ли это будет, чем обычные int-ы? Для быстрого прототипирования сойдет. А потом можно будет в using-ах int-ы заменить на какой-то из вариантов strong typedef и компилятор еще и сам по рукам разработчикам бить начнет, когда они радиус с шириной начнут путать по недосмотру.
А примеры как испортили код отсутствием using там, где в этом нет явной на то необходимости?
Легко. Во-первых, уже был пример с std::shared_ptr<std::vector<std::unique_ptr<Shape>>>. Вместо которого был бы ShapeContainerShptr.
Во-вторых, давайте исходный пример чуть расширим и добавим в Canvas методы extract, insert и replace. Без алиаса получим (для простоты не расставлял [[nodiscard]]):
Любой желающий может сам оценить какой из вариантов больше замусоривает смысл более низкоуровневыми деталями реализации. И какой более приспособлен к будущим изменениям. Например, если нам потребуется сменить обычный unique_ptr на unique_ptr с кастомным делетером.
Теперь ваши примеры ситуаций, когда алиасы мешают.
Остается переименовать int в Signed
Вам смешно, а я вот сейчас работаю с проектом, в котором для индексации задействовали int-ы, а не какой-либо из вариантов strong typedef. Поменять это задешево уже не получается и приходится разгребаться с предупреждениями о неявных конвертациях size_t в int, а иногда и double в int (промежуточно через size_t). Был бы изначально введен некий ItemIndex, пусть даже в виде простого using-а, сейчас стало бы гораздо проще.
А что пользоваться этим невозможно, так кому какое дело.
Интересно почему этим невозможно пользоваться?
Что, конечно же, вас ничуточку не переубедит
Есть немаленькая вероятность, что я программирую дольше, чем вы живете на свете. И есть еще большая вероятность, что говнокода пришлось разгрести тоже побольше. Так что да, не убеждают. А вот ощущение, что вы сперва сказали ерунду, а потом ее старательно защищаете, только усиливается.
Кстати говоря, профнепригодность относилась к совету не использовать алиасы в простых случаях. А не то, что вы написали выше.
Ну а я насмотрелся на тех, которые пихают typedef и using где надо и не надо.
Примеры можно? Что-то мне сложно представить, как using-ами можно код испортить.
Может из OpenSource что-нибудь?
Если у вас программисты пишут плохой код потому что они не используют using почем зря -- дело совершенно точно не в using.
В том-то и дело, что не у меня. Почитаешь профильные ресурсы, все профи просто высшего разряда. Как придешь какой-нибудь проект консультировать, так просто в шоке -- где все те монстры от программирования, которые себя пяткой в грудь в комментариях бьют. И код корявый получается не потому, что тупые неумехи его пишут, а потому, что просто вовремя не научили, как можно себе жизнь облегчить, а код -- упростить.
Это, положа руку на ногу, просто бесполезная перестановка символов местами, которая выигрывает несколько символов
Блин, еще раз повторяю: экономия символов здесь не при чем от слова совсем.
Зачем?
Затем, что именованные типы (даже в виде алиасов) служат как дополнительный слой абстракции и скрывают лишние детали. Когда вам потребуется узнать что за ShapeUptr -- вы посмотрите. А пока это не потребовалось, то лучше использовать одно имя вместо std::unique_ptr.
Просто потому что.
Если вы чего-то не понимаете, то это не значит, что в этом нет смысла. Аргументов вам здесь уже привели в ассортименте. Причем разные люди.
однобуквенные, но значимые идентификаторы. Запоминать чем отличается U<T> от SH<T> от I<T> от P<T> и прочих одно-двух-трех-буквенных индентификаторов такое себе удовольствие. Еще хуже смотреть на код с такими вещами, особенно когда взгляд замыливается от усталости;
угловые скобки, которые никуда не деваются и о которые ты все равно спотыкаешься. Может показаться, что SH<std::vector<U<Shape>>> -- это сильно лучше, чем std::shared_ptr<std::vector<std::unique_ptr<Shape>>>, но это, как по мне, тот же самый фрагмент автопортрета Фаберже, только в профиль;
ну и главное, если со временем потребуется заменить тип за ShapeUptr на какой-то другой (вместо простого std::unique_ptr на какой-то хитрый собственный тип указателя), то имя ShapeUptr все равно остается на месте.
Вы меня конечно извините, но это аргументация уровня "нет ты".
Так если вы наговорили ерунды, то единственное, что можно сказать -- это назвать ерунду ерундой.
Я насмотрелся на программистов, которые не используют using-и. И еще больше насмотрелся на результаты их работы. Так, что больше не хочется. После этого любой персонаж, который мне начинает рассказывать про то, что "любой алиас -- это сокрытие типа", просто расписывается в своей профнепригодности. Пардон май френч.
Как например использование emplace_back() вместо push_back()
Для emplace_back есть очевидный сценарий применения -- это когда у нас на руках есть набор аргументов для конструирования нового объекта в векторе. Типа такого:
Только вот это необходимое, но недостаточное условие.
А вы програмируете только так, как написано в учебнике?
Однако, у вас есть нехорошая привычка не отвечать на вопросы. А именно:
Про какую именно ошибку речь? Про то, что нет
string_view<char>
?Ну так в комментариях никто не обязан сходу писать компилирующийся и линкующийся код. Можно считать, что там написано либо:
либо
суть это не меняет.
В стандарте много чего есть, но как-то оно не сильно помогает писать код без ошибок.
Т.е. если код написан на C++, но оказался неправильным, то это не C++ код?
Ой, как удобно!
И какой же это код? Неужели Си-шный?
Когда вектора -- это еще неплохо, бывает что и векторов нет, а люди бабахаются с голыми new, а то и malloc-ами. Только вот это же про говнокод, а не про язык. В языке-то давно средства есть, и рекомендации по их использованию.
Да хотя бы из-за невнимательности программиста. Сохранить где-то ссылку на временную переменную, которая протухает при выходе из скоупа -- как два байта. Вы запросто это можете получить даже на обычном std::max.
Блин, да вы бы читать научились, раз уж приводите цитату из GSL. Там же прямое противоречие вашему утверждению про "распространенный совет -- предпочитать std::vector ... и статическим векторам".
Совет предпочитать использовать std::vector С-шным массивам, действительно, очень давний. ЕМНИП, Страуструп дает его начиная с 3-го издания своей книги "Язык программирования C++", которое вышло уже после появления и стандартизации STL. Но, во-первых, относился он в большей степени к массивам, которые меняют свой размер в динамине. И, во-вторых, с тех пор уже поколение выросло, а в STL уже почти полтора десятка лет есть std::array. Надо бы делать проправки на современные реалии.
Если говорить о технической составляющей, то применение std::launder для заявленных вами целей выглядит избыточным и в C++17. std::launder предназначен для других целей, следовательно, не нужен. А если мы полагаемся на то, что пока имеющиеся компиляторы не эксплуатируют данный UB, то достаточно простого reinterpret_cast-а.
И если уж и писать статью, то про какой-то условный:
Простите за прямоту, но этого в статье нет от слова совсем.
Стоит, поскольку в статье нет достаточного количества предупреждений о том, чем это может потенциально грозить. Как и нет рекомендаций о том, чем следует заменить этот хак опираясь на свежие стандарты.
Формально они-то как раз и не рабочие. И никто не даст вам гарантии, что условный clang-25, который начнет эксплуатировать данный UB, оставит старое поведение для C++17. Т.е. если кому-то взбредет в голову использовать описанный вами подход в своем коде, то это будет похоже на закладку мины замедленного действия. Работает, работает, о том, как оно сделано, уже никто и не знает, т.к. автор ушел в закат пару лет назад. В один прекрасный момент случается обновление компилятора и разбирайся потом почему перестало работать то, что работало.
Так что не вводите в заблуждение читателей. То, что вы описываете -- это хак, который пока что работает, т.к. до C++17 не было std::launder и компиляторщики не позволяли себе эксплуатировать этот UB. После C++20 и C++23 руки у них развязаны. И хак этот не имеет отношения к " гибким инструментам управления памятью и типобезопасностью".
Думается, у вас ошибка в тексте:
Вместо часто работает должно было бы быть пока еще работает
После добавления в язык std::start_lifetime_as у компиляторщиков развязаны руки для того, чтобы начать эксплуатировать UB, на котором построен ваш код.
Статье очень сильно не хватает примеров кода, которые бы иллюстрировали происходящее.
Из которых только около 1% на gitlab-е.
Давайте будем точными: речь шла про MR, а не про PR.
И да, непонятно, если я не пользуюсь gitlab-ом от слова совсем, то чем это в 2025-ом отличается от 2015-го?
Бесконтрольные forward decl, к сожалению, прямой путь к хоть и мелкому, но геморрою. Такие определения следует держать в одном месте, а тогда и using-и не проблема от слова совсем.
Редкое, но не то, чтобы исключительное. Например, для unique_ptr -- был простой, стал unique_ptr с кастомным deleter-ом. Для shared_ptr -- был обычный std::shared_ptr, поменяли на кастомный без поддержки weak-references и с простым счетчиком ссылок место атомарного (по типу Rc в Rust, который отличается от Arc). Или же был обычный std::shared_ptr, а стал каким-нибудь boost::intrusive_ptr.
А давайте сделаем вашу попытку пошутить более серьезной и возьмем такой пример:
Очевиднее ли это будет, чем обычные int-ы?
Для быстрого прототипирования сойдет. А потом можно будет в using-ах int-ы заменить на какой-то из вариантов strong typedef и компилятор еще и сам по рукам разработчикам бить начнет, когда они радиус с шириной начнут путать по недосмотру.
Легко. Во-первых, уже был пример с
std::shared_ptr<std::vector<std::unique_ptr<Shape>>>
. Вместо которого был быShapeContainerShptr
.Во-вторых, давайте исходный пример чуть расширим и добавим в Canvas методы extract, insert и replace. Без алиаса получим (для простоты не расставлял
[[nodiscard]]
):и тоже самое с алисом:
Любой желающий может сам оценить какой из вариантов больше замусоривает смысл более низкоуровневыми деталями реализации. И какой более приспособлен к будущим изменениям. Например, если нам потребуется сменить обычный unique_ptr на unique_ptr с кастомным делетером.
Теперь ваши примеры ситуаций, когда алиасы мешают.
Вам смешно, а я вот сейчас работаю с проектом, в котором для индексации задействовали int-ы, а не какой-либо из вариантов strong typedef. Поменять это задешево уже не получается и приходится разгребаться с предупреждениями о неявных конвертациях size_t в int, а иногда и double в int (промежуточно через size_t). Был бы изначально введен некий ItemIndex, пусть даже в виде простого using-а, сейчас стало бы гораздо проще.
Интересно почему этим невозможно пользоваться?
Есть немаленькая вероятность, что я программирую дольше, чем вы живете на свете. И есть еще большая вероятность, что говнокода пришлось разгрести тоже побольше. Так что да, не убеждают. А вот ощущение, что вы сперва сказали ерунду, а потом ее старательно защищаете, только усиливается.
Кстати говоря, профнепригодность относилась к совету не использовать алиасы в простых случаях. А не то, что вы написали выше.
Примеры можно? Что-то мне сложно представить, как using-ами можно код испортить.
Может из OpenSource что-нибудь?
В том-то и дело, что не у меня. Почитаешь профильные ресурсы, все профи просто высшего разряда. Как придешь какой-нибудь проект консультировать, так просто в шоке -- где все те монстры от программирования, которые себя пяткой в грудь в комментариях бьют. И код корявый получается не потому, что тупые неумехи его пишут, а потому, что просто вовремя не научили, как можно себе жизнь облегчить, а код -- упростить.
Блин, еще раз повторяю: экономия символов здесь не при чем от слова совсем.
Затем, что именованные типы (даже в виде алиасов) служат как дополнительный слой абстракции и скрывают лишние детали. Когда вам потребуется узнать что за ShapeUptr -- вы посмотрите. А пока это не потребовалось, то лучше использовать одно имя вместо std::unique_ptr.
Если вы чего-то не понимаете, то это не значит, что в этом нет смысла. Аргументов вам здесь уже привели в ассортименте. Причем разные люди.
Минусы такого подхода:
однобуквенные, но значимые идентификаторы. Запоминать чем отличается
U<T>
отSH<T>
отI<T>
отP<T>
и прочих одно-двух-трех-буквенных индентификаторов такое себе удовольствие. Еще хуже смотреть на код с такими вещами, особенно когда взгляд замыливается от усталости;угловые скобки, которые никуда не деваются и о которые ты все равно спотыкаешься. Может показаться, что
SH<std::vector<U<Shape>>>
-- это сильно лучше, чемstd::shared_ptr<std::vector<std::unique_ptr<Shape>>>
, но это, как по мне, тот же самый фрагмент автопортрета Фаберже, только в профиль;ну и главное, если со временем потребуется заменить тип за ShapeUptr на какой-то другой (вместо простого std::unique_ptr на какой-то хитрый собственный тип указателя), то имя ShapeUptr все равно остается на месте.
Так если вы наговорили ерунды, то единственное, что можно сказать -- это назвать ерунду ерундой.
Я насмотрелся на программистов, которые не используют using-и. И еще больше насмотрелся на результаты их работы. Так, что больше не хочется. После этого любой персонаж, который мне начинает рассказывать про то, что "любой алиас -- это сокрытие типа", просто расписывается в своей профнепригодности. Пардон май френч.
Для emplace_back есть очевидный сценарий применения -- это когда у нас на руках есть набор аргументов для конструирования нового объекта в векторе. Типа такого:
Для добавления же в конец вектора готового объекта предназначен push_back.
Все просто и очевидно. Использовать emplace_back как замену push_back -- ну такое себе, "сомнительно, но окай" (с)