Pull to refresh
86
0.7
Евгений Охотников @eao197

Велосипедостроитель, программист-камикадзе

Send message

Раз пошла такая пьянка про подавляющее большинство программистов, то это самое подавляющее большинство не в состоянии писать на C++ безбажный код.

Исходя из моего (пусть и небольшого) опыта программирования на VisualBasic, Java и Ruby, это утверждение истинно не только для C++.

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

Я просто приведу здесь вашу же цитату:

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

Если "либо отказаться от многих новых возможностей" не относится к "C++ному коду вообще", то мне остается только попросить вас более четко излагать ваши мысли, чтобы кривотолков не возникало.

А что они читают, на каких материалах учатся?

А это не важно. Поинт в том, что нельзя ориентироваться по тому, что пишут в Интернете. Во-первых, потому, что пишут в Интернете далеко не все, а только небольшой процент от причастных к профессии. Во-вторых, потому, что круг нашего чтения складывается под наши собственные предпочтения. Так, если отталкиваться от моего круга чтения, то чуть ли не каждый второй может написать Boost.Hana или Boost.Spirit.

Дались Вам эти платформы.

Так это же вы сетуете на высокие накладные расходы C++ного кода. Значит вам этого не хватает и вам приходится опускаться сильно ниже, что подразумевает хорошее знание конкретной платформы. Отсюда и сомнение в том, что некий C++ный разработчик кроме своей прикладной области еще и будет сильным экспертом в нескольких аппаратных платформах. Т.е. такие люди определенно есть, но их совсем немного.

И ради бога, если эта разница не ощущается пользователем непосредственно.

Боюсь без конкретики все разговор опять зайдет про сферических коней в вакууме.

Я вам пример привел: работа с JSON. В которой шаблонов очень много. Но мало кого парят накладные расходы, связанные с использованием шаблонов в этой области.

В том-то и дело, что примеры искусственные. И, что важно, нужно сравнивать объем и качество кода с шаблонами и объем и качество кода без оных на решении одних и тех же задач.

Вот уже упомянутый выше RapidJSON. Весь на шаблонах. Можно попробовать сделать его конкурента без шаблонов и посмотреть во что все это выльется.

Да, работаю в одиночку, но разные сообщества читаю регулярно.

Огромное количество разработчиков вообще не читает ресурсов типа Habr или reddit. А из тех, кто читает, очень небольшой процент ничего не пишет (ни статей, ни комментариев).

Не вижу связи.

Связь в том, что C++ позволяет писать переносимый высокоуровневый код, который будет работать настолько эффективно, насколько смогли сделать разработчики компилятора под эту платформу. И, если брать в рассмотрение сразу несколько платформ, то не так уж много шансов, что конкретный прикладной программист будет настолько же хорошо знать ассемблер x86 и условного ARM-а, как это знают компиляторостроители. Огромному количеству C++ников это просто не нужно.

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

Или о том, что был выбран неэффективный алгоритм.

Я вполне понимаю и даже уважаю позицию "мы знаем, что такой стиль приводит к значительным накладным расходам, но считаем их оправданными потому, что ...". Но чаще люди просто не понимают, насколько их код избыточен - ни по его виду, ни по функциональности.

Боюсь, здесь разговор пошел о каких-то сферических конях в вакууме.

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

Та же самая работа с JSON может выглядеть очень по разному. Можно взять шустрый RapidJSON (который весь на шаблонах) и написать 100500 строк кода вокруг него, т.к. RapidJSON дает ну очень низкоуровневый интерфейс. Можно взять более тормознутый nlohmann::json, с которым будет написано всего 1500 строк кода. Как раз за счет шаблонов. И разница по производительности в несколько раз не будет играть никакой роли, если работа с JSON-ом не лежит на критическом пути.

Но тут встает вопрос - не слишком ли много на себе берет компилятор?

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

Это C++, таков путь.

И могу ли я сказать компилятору, что я сам готов следить за временем жизни своих объектов и не надо это делать за меня.

Начиная с C++20 компилятор сам уже достаточно умен, чтобы понимать в некоторых случаях, что пользователь неявно начинает время жизни для объекта. Ссылка на пропозал была выше. Начиная с C++23 для этих целей завезли start_lifetime_as.

На самом деле (жирное ИМХО) это был косяк комитета: эту практику, сколь уж она считалась UB, давным-давно нужно было узаконить. Но руки дошли только в рамках работ над C++20.

До этого многие (включая меня самого) даже не знали, что такое поведение является UB.

В чем причина UB?

Насколько я понимаю, причина в том, что компилятор C++ должен уметь отслеживать начало и завершение времени жизни объекта. Например:

some_struct s1; // Здесь очевидное начало lifetime для объекта.
some_struct * p1 = new some_struct{}; // Здесь еще одно очевидное начало lifetime.
some_struct * p2 = new(&s1) some_struct{}; // Здесь очевидное начало жизни для p2.

В случае, когда мы тупо делаем reinterpret_cast или C-style case, время жизни ни для какого объекта не начинается.

Ну, блин. Это не UB. Это просто явный косяк.

Вы не за то зацепились. Проблема не в значении 53. Проблема в приведении типа. В чистом Си вот это не UB:

some_struct * p = (some_struct *)(some_ptr + some_offset);

тогда как в C++ этот же код:

some_struct * p = (some_struct *)(some_ptr + some_offset);

будет иметь UB. Этот UB не эксплуатировался компиляторами до C++20 (скорее даже до С++23), тогда как начиная с C++23 никаких гарантий по этому поводу уже нет. Не поставил std::start_lifetime_as, ну значит ССЗБ.

В данном конкретном случае явный кастинг может стать причиной UB только потому, что компилятор начинает неправильно что-то там оптимизировать.

Оптимизатор не может стать причиной UB. Оптимизатор может воспользоваться оставленным пользователем UB. Например, в C++ (если я не запутался в нововведениях), вы не можете просто так написать код вроде:

char data[1024];
read_data(data);
some_struct * payload = reinterpret_cast<some_struct *>(data + 53 /*пропустили заголовок*/);

В чистом Си можете, а в C++ начиная с C++23 для этих целей следует применять std::start_lifetime_as.

Потому что порядок следования байт и выравнивание мы устанавливаем руками одинаковое с обеих сторон.

У "выравнивания" есть две составляющие в данном вопросе:

Первое: упаковка данных внутри буфера. Так, если у вас структура вида:

struct header {
  uint32_t _magic_number;
  uint8_t _version;
  uint16_t _fields;
  uint64_t _flags;
};

то без #pragma pack для вашей структуры в зависимости от настроек компилятора между _version и _fields может быть "дыра".

Второе: выравнивание адресов для вашей структуры на принимающей стороне. Тот самый alignas из современного C++. Без оного фокус вида:

char data[sizeof(header)];
read_data(data);
header * h = (header *)data;
if(0xDEADBEAF == h->_magic_number) // OOPS!

может аварийно завершится на некоторых платформах в точке "OOPS".

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

В компиляторах, конечно же, встречаются ошибки. Но, как правило, если программа в релизе с оптимизациями не работает, то с 99% вероятностью это проблема кода пользователя, а не качества компилятора.

Вот поэтому критичные по скорости куски кода писали на С.

Если вы думаете, что в чистом Си нет UB, то вам нужно прочитать хотя бы вот эту серию статей: https://habr.com/ru/articles/341048/ (это первая, в ней ссылки на остальные).

Если очередная версия компилятора превращает работающий код в ХЗ что, то нужен ли такой компилятор?

Это перпендикулярный вопрос. Пока же мы живем в реальности, в которой в стандарте C++ определен ряд UB и компиляторам разрешено эти UB эксплуатировать. Что и происходит и, временами, ведет с потере работоспособности кода и последующим бурлением говн в этих наших Интернетиках.

ЕМНИП, одним из UB, в частности, было то, что до C++20 вот такой вот код:

alignas(DataHeader) char buffer[sizeof(DataHeader)];
read_data(buffer);
DataHeader * header = reinterpret_cast<DataHeader *>(buffer);

содержал в себе UB.

Поскольку эта практика была распространенной, такой UB не эксплуатировался компиляторами. Но в будущем запросто может. Поэтому в C++20 этот UB устранили для нескольких случаев (включая показанный в примере выше), но для других случаев подобные касты все равно остаются UB.

Эт да, но это одна из самых очевидных проблем, которую легко обойти, если делать преобразование представления прямо "по месту". Веселее с неочевидными, когда люди, например, не знают про выравнивание данных и директивы вроде #pragma pack.

И ради чего все это? Вот чтобы что?

Чтобы в коде не было UB и чтобы очередная версия компилятора не превратило результат трансляции вашего cast-а в ХЗ что.

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

Если у вас свежий C++, то в C++20 уже были сделаны некоторые послабления.
А в C++23 завезли std::start_lifetime_as.
Проблем с выравниванием данных это не решает (тут вы правы на счет переносимости), но хотя бы избавляет код от UB.

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

Жаль, что здесь есть только две оценки для комментария: +1 и -1.
Очень, очень не хватает оценки facepalm. Хотя здесь бы более уместной была бы double-facepalm.

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

Раз пошла такая пьянка, то по моим личным ощущениям (моим личным, это важно) C++ники вообще никому не нужны.

Это пойдёт как уже значимая выборка? 3-5%.

Да.

Но при крайне слабом контроле качества кода это всё превращается в невнятную кашу.

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

Так что предполагаю, что не шаблоны тому виной. Они лишь катализатор (в плохом смысле этого слова).

Как бы да, и я стараюсь об этом прямо сказать.

Но, позволю себе немного растечься мыслею. Сама статья весьма специфична и в ней речь идет о (кмк) сравнительно узкой сфере задач, решаемых на C++. Тем не менее, она актуальна в том плане, что автор статьи рассказывает о вещах, которые появились a) недавно (так мне показалось) и b) как способ преодолеть ограничения, достигнутые при применении универсальных языков (C++ в частности). Т.е. сперва задачи из предметной области автора статьи решались на C++ (и конкурентов практически не было), но времена меняются и сейчас вне C++ можно достичь результатов лучше, чем на C++.

Здесь все OK. С поправкой на то, что не нужно специфику узкой области автора статьи экстраполировать на вообще весь C++ и сферы его применения (что, кмк, происходит в комментариях, автор статьи такой попытки и не делал).

Но вот появляется персонаж, который в очередной раз (ну вот реально он не впервые прибегает с RPG в комментарии к статьям про C++) говорит: "А вот RPG в моих задачах рвет C++ как тузик грелку". И это не имеет отношения к поднятой в статье теме просто потому, что RPG специально затачивался под такие задачи, и рвет он не только C++, но и вообще все за исключением COBOL-а (с той же самой платформы).

Мало того, что данный персонаж со своим RPG уже поднадоел, так еще и непонятно какие выводы из его историй стоит сделать (и это принципиально отличает обсуждаемую статью). Ближайшая аналогия: если вам нужны регулярки, то используйте готовые regex-а, а не костыльте их самостоятельно. Ну как бы да, спасибо, Кэп.

Ну так о чем и речь.

Тогда повторю свой вопрос: а при чем здесь C++?

Зачем вы именно C++ противопоставляете "наиболее эффективному инструменту под задачу"?

Я сравниваю сравнимое.

Да я тоже могу на C++ сделать DSL для описания регулярного выражения, а потом сравнить его результат с выхлопом, скажем, re2c. Или сделать на C++ вручную bottom up LR(1) парсер и сравню его с bison-ом.

Не, ну а чё? Сравнимое же.

А потом еще и буду говорить, но вот когда нужно делать обработку 2D изображений, тогда, конечно же, С++ круче, чем re2c и bison вместе взятых.

Получится как у вас с вашим любимым IBM i.

Я могу понять, когда люди сравнивают C++ и, скажем, Java для реализации проектов типа NetBeans или Eclipse. Когда компонентная архитектура, плагины от разных разработчиков. И хочется, чтобы все это работало и быстро, и надежно.

1
23 ...

Information

Rating
1,346-th
Location
Гомель, Гомельская обл., Беларусь
Registered
Activity