Warning: в связи с волной, поднявшейся в комментариях, заранее прошу: прежде чем комментировать, дочитайте, пожалуйста, статью до конца.
Так уж случилось, что я пишу код для разных IoT-железок, связанных с электричеством, типа зарядных станций автомобилей. Поскольку аппаратных ресурсов, как правило, вполне достаточно, то основным фокусом является не экономия каждого байта и такта процессора, а понятный и надежный код. Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++ в его современном варианте - C++17, активно поглядывая на фичи из стандарта 20-го года и новее (подождите, кто сказал Rust?).
Иногда запускаются новые проекты на той же платформе, с теми же процессами и с переиспользованием многих уже существующих компонентов, и тогда в эти проекты мы ищем программистов, с учетом вышесказанного - программистов на C++. В embedded, тем не менее, чистый C все еще очень популярен, и нередко собеседоваться на вакансию C++ Developer'а приходят именно сишники. Логика у человека простая: языки, на первый взгляд, довольно близкие и почти обратно-совместимые, базовый синтаксис одинаков, про ООП кандидат что-то слышал, и значит, основная база уже есть и он сможет легко освоить C++ за 21 день в процессе работы, поэтому можно наплести про "с C++ тоже работал", начать писать на "Си с классами" и все получится. В то время как в новой команде таких "бывших сишников" уже и так набралось несколько, и такой кандидат нам уже не подойдет, на оставшиеся позиции нужен именно опытный плюсовик-затейник, который будет активно внедрять best practices и наставлять на code review на путь истинный менее опытных коллег.
К счастью, несмотря на кажущуюся схожесть языков, чем лучше ты знаешь каждый из них, тем больше ты понимаешь, что они очень разные, и в итоге разработчика на C от разработчика на C++ можно легко отличить на интервью или ревью, и команда даже на основе своего опыта и своих предпочтений набросала список звоночков, которые выдают сишников при собеседовании на C++-позицию и вызывают необходимость уже более детального разговора на тему "а почему ты сделал так". Итак, признаки того, что разработчик программирует не на C++, а на "C с классами":
Использует <stdint.h>, <string.h>, <stdio.h> вместо <cstdint>, <cstring>, <cstdio>;
Использует malloc() и free() кроме явно предназначенных для этого мест (типа кастомных аллокаторов);
Использует ручное управление памятью с new и delete, вместо RAII и умных указателей;
Использует char*-строки и функции <string.h> вместо std::string и std::string_view. (единственное исключение - строковые константы через constexpr). Использует функции из <time.h> вместо std::chrono. Использует atoi() вместо stoi(). Использует функции из <stdio.h> вместо std::filesystem и потоков ввода-вывода. Использует <pthread.h> вместо std::thread.
Когда нужно имплементировать алгоритм или контейнер независимый от типа данных, которыми он оперирует, использует #define-макросы или void*-указатели вместо темплейтов;
Для объявления констант использует #define вместо const и constexpr;
Использует C-style массивы вместо std::array;
Использует NULL вместо nullptr;
Пишет (type)something вместо static_cast<type>(something);
Использует простые указатели на функции вместо std::function;
Использует константные enum вместо enum class даже для простых перечислений;
Для функций, не изменяющих состояние объектов, не использует const при объявлении. Для конструкторов забывает explicit. Для деструкторов забывает virtual :)
При разработке в ООП-стиле, объявляет все члены класса как public;
Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки), то одно из них возвращает через return, а другое - по указателю или по неконстантной ссылке, вместо использования std::optional, std::pair/std::tuple (особенно хорошо в паре со structured binding) или просто возврата struct;
Объявляя новую переменную с типом-структурой везде пишет struct в имени типа, или наоборот, при объявлении новой структуры пишет typedef struct вместо просто struct;
Не использует неймспейсы при структурировании кода;
Использует union вместо std::variant (кстати, для каламбура типизации использовать union тоже нельзя, он нарушает active member rule);
Пишет реализации общеиспользуемых алгоритмов (foreach, transform, find_if, sort, lower_bound, и т.д.) вручную даже если они есть в <algoritm>;
При простой итерации по элементам контейнера пишет многословные конструкции вместо range-based for. Не использует auto и using в многословных конструкциях типов;
Плюс немного дополнений из комментариев:
Использует битовые поля вместо std::bitset
Использует си-шные библиотеки на прямую без уровня абстракции над ней
В заголовочных файлах куча инклудов, которые можно было в принципе там и не писать (incomplete class)
Если вы матерый плюсовик, и при чтении этого списка у вас полыхает несогласие с некоторыми из этих пунктов и бурлит желание поспорить - это отлично, значит вы действительно матерый плюсовик. А для остальных, пожалуй, добавлю очевидную оговорку, что для многих описанных практик есть исключения и все зависит от конкретной ситуации. Например:
у вас может быть очень много соприкосновений с чисто сишными библиотеками;
проект может использовать древний тулчейн, умеющий только C++98 (правда, при работе в таких проектах нужно требовать тройную оплату и дополнительную страховку за вредность, а лучше вообще избегать подобного);
вы используете Qt, где своя модель владения, и new там используется на каждом шагу;
std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);
абстракции рано или поздно протекают: вы не сможете создать std::fstream из существующего и открытого posix file descriptor (хотя некоторые реализации stdlib такое умеют), а средствами <thread> вы не сможете задать приоритет потоку, и еще много чего;
Но да, это, все-таки, частные случаи, и естественно, если человек может грамотно обосновать использование или неиспользование той или иной языковой конструкции или апишки - то это уже говорит о его понимании и должно зачитываться только в плюс.
Комментарии (404)
Koval97
26.10.2021 13:47+8На месте директора я бы сразу уволил такого менеджера в 24 часа, с пометкой "Больше его к техническим специалистам не допускать". И мне все равно, что всего за один дурацкий заголовок.
F0iL Автор
26.10.2021 13:48+5А менеджеры в технических собеседованиях и принятии решения и не участвуют, собеседуют и определяют требования сами технические специалисты этого и соседних проектов. О причинах такого подхода я написал в начале статьи.
А для всех остальных, кто не делает выводов по заголовку, а сначала читает содержимое статьи: если вы тот самый матерый плюсовик-затейник, умеете готовить Yocto и не прочь переехать жить и работать в Чехию - черканите мне, возможно нам есть о чем пообщаться :)
Koval97
26.10.2021 14:59+1Я скорее энтузиаст, и предпочитаю использовать максимум API нежели готовые framework-и. Да, я художник, а не биоробот и с такими работаю максимум подрядом. Потому что не работаю с помешанными на перфекционизме диктаторами. Как я уже сказал, без зазрения совести такого менеджера нельзя допускать к разработке, даже если он карьерист и уже Team Lead. Либо ваш диструктивный подход скорее вредит разработчикам, чем помогает. И о вас уже есть целая статья.
Вот когда станете в ходе работы над собой более конструктивным, научитесь созидать, а не разрушать, то тогда можете написать, предложите интересный сложный проект, может и поработаю с вами. Я уже к тому времени проект выложу, над которым работаю сейчас.
F0iL Автор
26.10.2021 15:07+4Я ее понимаю, почему вы уже второй комментарий подряд говорите про "менеджеров". Всё то, что я изложил выше - это не решения менеджеров и "тимлидов", менеджеры и тимлиды тут вообще ни коим боком, их нету в контексте. Это именно коллектив инженеров одной компании, многие из которых тащили смежные проекты на протяжении десятка лет от PoC до зрелости и повидали некое дерьмо, сами разработчики в итоге в процессе решения проблем и обсуждений совместно (и демократически) пришли к определенным выводам, сформулировали стандарты для платформы и обозначили хотелки для расширения команды.
victor_1212
26.10.2021 16:32+5>.... когда станете в ходе работы над собой более конструктивным
imho, все просто, это статья написана не для вас (и не для меня), честно говоря не совсем понимаю для кого она, но это не повод для волнений, опыт всегда прав, но у разных людей в разное время :)
ncr
26.10.2021 14:04+39Действительно, полыхает, но от того, что половина списка — карго-культ:
Используете простые указатели на функции вместо std::function;
Это очень разные инструменты для очень разных случаев. Советовать слепо применять аллоцирующий полиморфный враппер независимо от того, нужен ли он — это шикарно само по себе, даже если забыть о том, что половина случаев с указателями на функции — это интероп с C и std::function туда не пролезет при всем желании.
Используете простые enum вместо enum class;
Если у вас enum'ами задаются конкретные константы, которые часто надо куда-то передавать в числовом виде (тот же интероп), то от enum class больше попаболи, чем пользы.
Для функций, не кидающих исключения, не используете noexcept.
А вы можете доказать, что не кидающих? И что все, используемое в этих функциях, тоже некидающее? И используемое используемым all the way down? noexcept уместен в специальных функциях и критических местах, в остальных — опять же, больше попаболи, чем пользы.
Для конструкторов забываете explicit.
Для всех подряд?
Для деструкторов забываете virtual
Для всех подряд?
Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки)
+ 21. Если вы используете коды ошибок вместо исключений.
F0iL Автор
26.10.2021 14:20+7слепо
А про "слепо" никто и не говорил. Перечитайте последний абзац.
применять аллоцирующий полиморфный враппер
Аллоцирующий он далеко не всегда. Пример.
даже если забыть о том, что половина случаев с указателями на функции — это интероп с C
Интероп - это отдельная тема. Есть проекты, где чистого интеропа с сишными библиотеками почти нет, а если и есть, то во всех точках соприкосновения написаны C++-врапперы.
std::function туда не пролезет при всем желании
Ну, в редких случаях, кстати, пролезет через target().
Для всех подряд? ... Для всех подряд?
Естественно, не для всех подряд. Статья есть лишь краткое перечисление пунктов, а не огромный талмуд описывающий подробно все возможные случаи и границы применения. Кто знает - тот знает, кто не знал - откроет cppreference, стандарт языка и какого-нибудь Скотта Майерса и узнает.
+ 21. Если вы используете коды ошибок вместо исключений.
Во многих проектах исключения вообще выключены. Например, в Chromium.
ncr
26.10.2021 14:41+7Статья есть лишь краткое перечисление пунктов, а не огромный талмуд описывающий подробно все возможные случаи и границы применения.
Вы пишете "Как различить C и C++-разработчиков" и приводите безапелляционный список "делай так, а не так". А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".
F0iL Автор
26.10.2021 14:48+7безапелляционный
Перечитайте последний абзац статьи
А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".
Ну так если человек делает что-то только потому что так написано в интернете, не понимая зачем и почему, то тут никакие даже исчерпывающие "описания в интернете" не помогут :(
dvserg
27.10.2021 09:22+2Если Вы приходите в проект, то необходимо применять приемы, уже принятые ранее в проекте. Иначе Ваш код будет выглядеть кривой заплаткой на корпусе корабля.
F0iL Автор
27.10.2021 09:32+3Это совершенно логично. Разве я где-то утверждал обратное?
dvserg
27.10.2021 09:56+1Я хотел показать, что поиск кристально-чистого С++ - cника сродни поиска сферического коня. Многие коллеги в своем роде "мутанты" между этими двумя языками. И мне кажется намного важнее в случае рассмотрения кандидатур знание/умение применить перечисленные приемы, чем слепое безапелляционное следование им "я_типа_тру_С++". То есть важно смотреть не то, что по умолчанию применил человек при решении теста, а его способность решить поставленную задачу "с применением таких-то вещей".
F0iL Автор
27.10.2021 10:12+3И мне кажется намного важнее в случае рассмотрения кандидатур знание/умение применить перечисленные приемы, чем слепое безапелляционное следование им "я_типа_тру_С++".
Да, все верно. Я в статье этот момент специально отметил:
во многих best practices есть исключения и все зависит от конкретной ситуации.
...
и естественно, если человек может грамотно обосновать использование или неиспользование той или иной языковой конструкции или апишки - то это уже говорит о его понимании и должно зачитываться только в плюс.
Antervis
26.10.2021 17:31+6Советовать слепо применять аллоцирующий полиморфный враппер независимо от того, нужен ли он — это шикарно само по себе
std::function
аллоцирует только при наличии контекста (например, захвата из лямбд). Полным аналогом voidset_callback(std::function<int(int)>&& cb)
в чистом си будетvoid set_callback(int(cb*)(int), void* ctx, void(*ctx_deleter)(void*));
где ctx в общем случае тоже надо аллоцировать, ну и не забыть удалить. Так что совет "используйте std::function вместо коллбеков когда можете" более чем валидный.F0iL Автор
26.10.2021 18:08+2std::function
аллоцирует только при наличии контекстаИ даже при наличии контекста далеко не всегда, во многих реализациях там есть small size optimization. Пример выше уже скидывал.
ncr
26.10.2021 18:52+7Бывают каллбеки с контекстом, бывают без контекста.
Если он там есть — естественно, std::function проще, лучше и понятнее, чем любой ручной колхоз.
Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.
Поэтому все как всегда — используйте что угодно, если вы понимаете, как оно работает и можете себе это позволить.Antervis
26.10.2021 19:41Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.
Присмотритесь. В примере bar() отлично оптимизируется в noop, пара доп. инструкций в foo() и call std::__throw_bad_function_call() это проверка функции против null (то, чего сишная версия не делает, а если делать, то оптимизируется и там и там) а typeinfo сгенерился для всяких стектрейсов и убирается с --no-rtti. По сути весь оверхед сводится к заполнению std::function при инициализации, да и это важно только если ваш метод вызывает коллбек никуда его не сохраняя
tyomitch
26.10.2021 19:46+5Если против прыжка на NULL меня страхует MMU, то я не хочу тратить на проверку на NULL отдельные инструкции. Си позволяет их не тратить; а если хочется тратить, то позволяет и тратить.
Antervis
27.10.2021 06:43+2если в конкретном случае вы замерили перф и оказалось что да, лишний if в std::function тормозит, то конечно же, делайте на указателях. Вот только под общий случай такой кейс вообще даже близко не подходит. Ну и от себя добавлю, что на практике я куда чаще сталкивался с недостаточно гибким интерфейсом (и костылями как его следствием), чем с тормозами в хорошо предиктящемся if
sheckn
28.10.2021 13:19+1Вынужден вас расстроить: по крайней мере часть компиляторов вставляет эту проверку принудительно,.им им пофигу, что адрес 0x0 находится в секции с флагом x
0xd34df00d
26.10.2021 19:48+12А вы можете доказать, что не кидающих? И что все, используемое в этих функциях, тоже некидающее? И используемое используемым all the way down? noexcept уместен в специальных функциях и критических местах, в остальных — опять же, больше попаболи, чем пользы.
Зашел в комменты написать именно это.
Компилятор ничего не скажет, если вы из noexcept-функции вызовите не-noexcept-функцию. Это совершенно недружественно к рефакторингу, к обновлению используемых библиотек, и так далее, и разваливается в рантайме.
agmt
27.10.2021 08:54А есть объяснение, почему такую проверку не добавят в clang-tidy?
clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html ругнётся, если увидит явное кидание исключения (т.е. функции должны быть в header), но вызов `noexcept(false)` функции — не ошибка.
Казалось бы, проверить noexcept() всех вызываемых — легче, чем анализировать их код.
eao197
27.10.2021 08:41Если у вас enum'ами задаются конкретные константы, которые часто надо куда-то передавать в числовом виде (тот же интероп), то от enum class больше попаболи, чем пользы.
Можно ли этот тезис раскрыть более подробно?
ncr
27.10.2021 14:41+5Тезис уже достаточно подробно раскрыт в других комментариях, но ок.
Проблема простых enum'ов в отсутствии скопа и неявных преобразованиях, позволяющих смешивать разные enum'ы.
Комитет в своей бесконечной мудрости ловко решил обе проблемы одним выстрелом, добавив enum class.
Однако, это две сильно разные проблемы: отсутствие скопа — это (почти) всегда плохо, а вот неявное преобразование — плохо не всегда, однако его оторвали совсем и нельзя даже opt-in.
Традиционно enum используется для определения констант, как окультуренный аналог #define. Скоп здесь весьма уместен, дабы не пихать префиксы прямо в имена констант, и с легкостью внедряется банальным поиском и заменой. А вот с отстуствием неявного приведения типов все гораздо хуже: чтобы не писать static_cast на каждый чих, тип этого enum class надо протаскивать везде, где он используется, что в любом проекте сложнее helloworld порождает вопросы «а стоит ли оно того?». Проще обычный enum в struct или namespace положить.
Кроме того, enum используется еще и для определения флагов, а флаги не живут в ваккууме, их надо уметь комбинировать и разделять, поэтому надо не только протаскивать везде новый тип, но и перегружать операции над ним, что кажется легким только на первый взгляд.
Для понимания проблемы попробуйте переделать вот этот достаточно простой и идиоматичный код на enum class, желательно, не меняя слишком много (в реальном проекте миллион строк) и сохраняя идиоматичность (с кодом работает более одного человека):enum flags { flag1 = 0x1, flag2 = 0x2, flag3 = 0x4, }; int main() { auto state = 0; if (some_condition) state = get_some_external_state(); cout << "state is " << hex << state; if (state & flags::flag1) { // something } if (state & flags::flag2) { // something else } set_some_external_state(state | flags::flag3); }
eao197
27.10.2021 14:56+5а вот неявное преобразование — плохо не всегда
Это плохо всегда.
Традиционно enum используется для определения констант, как окультуренный аналог #define.
Традиционно где? В чистом Си?
Если мы говорим именно про C++, то в C++ использование унаследованных из Си enum-ов бесполезно чуть меньше, чем полностью (особенно после появления C++11 с enum class).
А дабы проблем, которую вы проиллюстрировали, не было, в C++ (еще со времен добавления в него namespaces) можно было делать так:
namespace flags { typedef unsigned short type; const type flag1 = 0x1; const type flag2 = 0x2; const type flag3 = 0x3; }
Получаем и скоуп, и фиксированный тип, который скрывается за "перечислением". И имеем те самые неявные преобразования, которые выходцам из Си так нравятся.
Antervis
27.10.2021 15:18+1Самый простой способ сделать нормальные флаги в с++ - std::bitset. Можно сделать тонкую обертку для enum class, переопределив operator[]. А все эти битовые трюки с флагами-степенями двоек были не от хорошей жизни придуманы.
eao197
27.10.2021 15:27Тем не менее, когда с таким приходится работать, то почему бы и нет. Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71. Ну и до C++11 в заголовочных файлах константы через std::bitset было не задать, разве что вместо констант писать inline-функции, которые возвращали бы экземпляры std::bitset.
Antervis
27.10.2021 16:27Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71
так у bitset'а есть конструктор от числа, можно в обертку добавить конструктор от initializer_list<my_enum>, вариантов уйма
eao197
27.10.2021 16:31Боюсь, что все они будут гораздо объемнее, чем простое задание целочисленных констант. Ну и на компиляцию этого будет уходить гораздо больше времени.
Опять же, если речь заходит о старом коде, значительная часть которого написана во времена C++98, если не раньше, то там таких продвинутых фич не будет.
klirichek
04.11.2021 05:12Тип то фиксированный, но общий и вполне интегральный.
Его наличие никак не помешает написать, например,
tCounter.store ( std::memory_order_release );
вместо
tCounter.store ( 0, std::memory_order_release );
eao197
04.11.2021 07:12+2Это бы простейший пример того, как в старых плюсах (старых -- это времен 1993-1994 годов, когда стандарта еще не было, а вот namespaces уже в компиляторах стали появляться) получить все тоже самое, что дает C-шный enum, но с парой дополнительных плюшек.
Чтобы обеспечить большую безопасность по типам в тех же старых плюсах (но уже когда там поддержка шаблонов появилась) можно было бы использовать шаблоны. Типа:
namespace type_safety { template<class Scalar, class Tag> class scalar_value { ... public: explicit scalar_value(Scalar v) ... {} ... Scalar raw() const {...} ... // Полный фарш включая перегрузку различных операторов. }; } namespace flags { struct flags_tag {}; typedef type_safety::scalar_value<unsigned short, flags_tag> type; ... }
Правда, у этого подхода были существенные недостатки.
Во-первых, все эти шаблоны негативно сказывались на скорости компиляции, а некоторые компиляторы при обилии шаблонов в коде могли тупо падать.
Во-вторых, нельзя было просто так в заголовочном файле размещать инициализированные константы для flags::type, т.к. можно было получить ошибку линковки.
Но там, где безопасность по типам была нужна, на такое вполне себе шли.
В старых плюсах у C-шного enum-а была большая область применения вот в таком контексте:
struct demo { enum { value = 1 }; ... };
без необходимости бодаться со static-полями и их инициализацией.
Но в современном C++ для всего этого уже есть чисто C++ные решения. Так что какую пользу от С-шного enum-а можно получить, например, в C++17 -- это лично для меня большой вопрос.
dimaaannn
28.10.2021 00:17+1Мне кажется, плюсовики - это такая секта.
Они готовы не только спросить о принципах ООП, но и уточнят в каком текстовом редакторе вы обычно работаете. Потому что не все из них "правильные".
Ну а навыком кодить на бумажке должен обладать каждый уважающий себя программист.elektroschwein
28.10.2021 16:52Они готовы не только спросить о принципах ООП, но и уточнят в каком текстовом редакторе вы обычно работаете. Потому что не все из них "правильные".
Ну у вас и фантазии.
Ну а навыком кодить на бумажке должен обладать каждый уважающий себя программист.
Вы забыли добавить, "...если его основным видом деятельности является кодинг на бумажке".
SinsI
26.10.2021 14:16+3>12. Используете простые enum вместо enum class;
К сожалению, enum class - неполноценная замена enum: в том же QT enum используются для хранения флагов и констант из них/проверок на их наличие, и этого нельзя достичь с использованием enum class без повсеместного ручного приведения типов. Хуже всего, что нет нормального удобоваримого способа достать из него значение (без приведения типов или третьестопных малоизвестных шаблонов).
>15 Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки), то одно из них вы возвращаете через return, а другое - по указателю или по неконстантной ссылки, вместо использования std::optional, std::pair/std::tuple (особенно хорошо в паре со structured binding) или просто возврата struct;
Есть ещё альтернативный вариант - сделать для этой функции класс, передавать параметры к ней как конструктор/использовать setter'ы, забирать результат через getter'ы. Очень удобно, когда хочется вернуть много отладочной информации и при этом не замусоривать синтаксис основного применения.
F0iL Автор
26.10.2021 14:27+2и этого нельзя достичь с использованием enum class без повсеместного ручного приведения типов
Можно перегрузить
&, |, &&, ||, ^, &=, |=
у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения. В GCC'шной стандартной библиотеке так, например, сделаны std::launch и std::filesystem::permissions.grondek
26.10.2021 14:47+10И это будет из пушки по воробьям. Синтаксический сахар должен уменьшать объем написанного кода, а не увеличивать его.
То есть, как у вас в конце статьи и написано: надо смотреть по конкретной ситуации.
0xd34df00d
26.10.2021 19:46+2Так в кутях и так макросы используются для объявления флагов, добавить туда операторы — мелочь.
Gordon01
26.10.2021 15:26+7Можно перегрузить
&, |, &&, ||, ^, &=, |=
у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.Можно, но зачем?
А я скажу, что после этого код превратится в легаси и в чем-то буду прав.
Если завтра к вам придет обычный разработчик, а не фанат плюсовой магии, как вы ему объясните, зачем, это было сделано?
F0iL Автор
26.10.2021 15:52+4Если получится обернуть это все в темплейт, чтобы не писать это boilerplate code каждый раз и не зависить от конкретного типа, то будет очень даже изящно.
А ответ на вопрос "зачем" будет точно такой же, как и на "зачем вообще придумали enum class": типобезопасность. Когда у вас у функции два аргумента, каждый из которых представляет собой битовую маску, или один битовая маска, а другой вообще какое-то целое число, то перепутав их местами в одном случае вы получите ошибку компиляции, а в другом случае компилятор молча схватает это и скомпилирует программу с ошибкой. Или, например, вы по ошибке сделаете | между членами совсем разных enum'ов. В одном случае это тоже вызовет ошибку компиляции, а в другом случае компилятор промолчит. И более того, совпасть может так, что работать все будет абсолютно корректно (если позиции битов битов для конкретно этих значений в этих разных enum'ов совпадают), но совершенно внезапно сломается когда внесут изменения в один из них. И это только один из примеров.
Gordon01
26.10.2021 16:48+5Не, вы меня не поняли.
Обычный разработчик (не фанат плюсоизмов), на большинстве языков из топ10 не сможет или не будет это делать. Он просто напишет функцию is_read_only().
Как вы этому человеку объясните зачем вы это делаете:
Можно перегрузить
&, |, &&, ||, ^, &=, |=
у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.0xd34df00d
26.10.2021 19:50+3У меня есть чувство, что пришедший из других языков человек куда раньше оторвет себе ноги о различные UB и до вашего вопроса просто не дойдет.
Gordon01
26.10.2021 20:40+3А код с бизнес-логикой писать хоть один из этих людей будет?
Один отстреливает себе ноги.
Другой расширяет язык, чтобы он в неочевидном месте неочевидным способом семантически был похож на другой язык
Третий разбирается в причинах трехэтажных ошибок шаблонов из-за того что он написал bit | flag для типа которому оператор | не перегружен его коллегой сверху
Последний спорит в комментах на хабре
elektroschwein
26.10.2021 20:47+5Если вам нужно без проблем и приключений "писать код с бизнес-логикой", то стоило взять не C++, и уж тем более не C, а упомянутые здесь Java или Python. Ну или Rust, но там придется воевать с borrow-checker'ом.
Gordon01
26.10.2021 21:09+3Нет, нельзя, скрпы эмбеддеров это запрещают!
Шутка. Я бы конечно все просто писал на расте. Я пробовал и под МК без MMU писать и под арм64, все отлично, удобно и комфортно.
Просто есть отдельная часть людей, которые считают что под МК надо писать только на Си, а под армы с MMU - на С++. Причины и доводы, как обычно, те же самые, но вешать ярлыки не будем.
Просто почему-то этих людей совершенно не смущают известные и успешные проекты, которые были сделаны не на С/С++.
А как бы мы жили без Java Card - вообще не понятно.
bonta
27.10.2021 07:51+2Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.
В общем С++ лучше, к тому же с активным развитием современных стандартов вряд ли Раст особо сможет потеснить ++, т.к. и там приемлемо безопасно и адеквантый синтаксис схожий с Java и C# - а это большой плюс при выборе яп.
Например когда Джавистам или Шарпистам потребуется высокая производительность - вряд ли они выберут Rust, но выберут С++ т.к. им его будет значительно проще освоить.
orion_tvv
27.10.2021 21:21у ржавого синтаксис по началу действительно странноватый. но это скорее из-за новых концепций, нежели синтаксических конструкций. аналогичные вещи на плюсах как правило выглядят более тяжеловесно. как яркий пример из относительно свежего оператор <=>
AnthonyMikh
27.10.2021 21:50+2Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.
А что конкретно, по вашему, не так с синтаксисом Rust?
AnthonyMikh
27.10.2021 22:24Я же спросил конкретно. А то и я могу сказать, что у C++ вырвиглазный синтаксис.
bonta
28.10.2021 07:54+1я написал своё мнение, т.к. лично для меня чем дальше язык по синтаксису от Си++ тем он уродливее (именно от ++, а не от родоначальника синтаксиса – Си). В т.ч. мне не нравятся и Питон например. Но нравятся Джава, Шарп и Делфи (хоть он и не из Си семейства, но за счет многословности – а многословность неплохо описывает сам язык без документации, читать Делфийский код можно без предварительного изучения я.п, что не скажешь о Расте или Питоне например).
А вот Си мне не нравится например, причем сильно. Потому что на больших проектах (например Gimp) или там OpenVPN хорошо видно что языку не хватает того что есть в ++, в следствии чего например усложненные интерфейсы по сравнению с ++ (по кол-ву входящих параметров), обилие похожих друг на друга интерфейсов, отличающихся префиком/постфиксом (т.к. нет шаблонов и перегрузок - то часто на каждый тип входящих параметров или возвращаемых значений делают отдельный интерфейс).
0xd34df00d
27.10.2021 04:31+8Насчёт этих людей не знаю, но для себя я как-то понял, что единственный способ сохранить рассудок — не писать на плюсах.
ncr
26.10.2021 16:26+4Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше
Можно, но «как раньше» не будет.
Если вы, например, перегружаете & для некоторого enum class, моделирующего битовые флаги, то из оператора вы, вероятно, тоже возвращаете этот же тип, а не какой-то голый int, иначе ради чего это все. Однако, enum class неявно не конвертится ни в bool, ни в int и добавить к нему это нельзя, поэтому вместоif (Value & my_flags::foo)
придется городитьif ((Value & my_flags::foo) != my_flags::zero_flag)
или вообщеif (flags::is_set(Value, my_flags::foo))
что, конечно, реализуемо и даже безопаснее, чем раньше, но выглядит неидиоматично и набирать эти простыни никакой радости.
enum class — это «хотели как лучше, а получилось как всегда».Gordon01
26.10.2021 17:17+2Вообще не особо понятно, зачем автор предлагает из монструозного и сложного С++ костылями в одном месте сделать простой и лаконичный С, если до этого целую статью объяснял какие сишники смешные, что не хотят менять свой работающий подход кодирования определенных вещей (совместимый по стилю и семантике с другими популярными языками) на очередной новый стиль С++420.
F0iL Автор
26.10.2021 17:24+4о не хотят менять свой работающий подход кодирования определенных вещей
Атож. Напоминает классику: "Программист на Фортране пишет на Фортране на любом языке".
Вообще не особо понятно, зачем автор предлагает из монструозного и сложного С++ костылями в одном месте сделать простой и лаконичный С
Вообще не особо понятно, зачем кто-то предлагает из монструозного и сложного C транслятором в одном месте сделать простой и лаконичный ассемблер (привет из 70-х).
NN1
26.10.2021 23:59+4Можно перегрузить "!" ????
enum class E { Zero, A }; bool operator!(E e) { return e!=E::Zero; } int main() { E e = E::A; if (!!e) { return 1; } }
apro
26.10.2021 15:54В Qt есть Q_FLAGS/
Q_DECLARE_FLAGS
для генерации типобезопасной обертки над enum class описывающим флаги.0xd34df00d
26.10.2021 19:56+1Когда я последний раз ковырял Qt, там были обычные enum, и с enum class оно не особо работало. Может, в шестерке поменяли, впрочем.
KotKotich
26.10.2021 14:28+12Опять евангилист всяких догм. Инструменты нужно подбирать в зависимости от задачи. А не применять тупо заученные догмы и запреты. Правильно вышестоящий комментатор написал "На месте директора я бы сразу уволил такого менеджера в 24 часа, с пометкой "Больше его к техническим специалистам не допускать"."
F0iL Автор
26.10.2021 14:28-3Бьёрна Страуструпа вы тоже "евангелистом всяких догм" назовете? :)
А если серьезно, то перечитайте, пожалуйста, внимательно последний абзац статьи, а то не смешно уже.
tyomitch
26.10.2021 18:36Бьярне (Bjarne); Bjørn -- тоже существующее имя, но другое.
F0iL Автор
26.10.2021 18:39Буду знать. Интересно, но в русскоязычном сообществе устоявшимся написанием почему-то стало "Бьерн", видимо, стараниями издательств: https://www.ozon.ru/person/straustrup-bern-253179/category/knigi-16500/
Rustacean
26.10.2021 14:40+1интересно, через сколько в комментарии к статье придут любители Rust?
Явная провокация. Ну вот к чему это было? Не зря в комментариях в основном критическое восприятие статьи.
SShtole
26.10.2021 15:01+28Используете функции из <time.h> вместо std::chrono.
Сразу вспомнил вот эту картинку:funny_falcon
26.10.2021 21:02C: gettimeofday(), clock_gettime() . Уверен, что и другие способы найдутся.
vient
26.10.2021 23:40+5Обе эти функции не из стандартной библиотеки C, на Windows, например, они не существуют.
funny_falcon
27.10.2021 00:58-1Но по факту, time уже почти ни кто не использует. Кроме уж совсем простых случаев. Если есть возможность, используют clock_gettime, т.к. только он возвращает MONOTONIC. Если реализация POSIX уж слишком древняя, мирятся с gettimeofday и возможным дрифтом часов, но зато хоть миллисекунды получаю
На Windows наверняка тоже есть способы. Легкий Гуглинг показывает GetSystemTime, GetSystemTimeAsFiletime и GetSytemTimeAsPreciseFileTime. И для монотонного времени QueryPerformanceCounter и QueryPerformanceFrequency .
В общем, начинает казаться, что уж лучше пусть будет несколько функций в стандартной библиотеке, чем несколько в нестандартной.
Кстати, POSIX - это стандарт, или не стандарт?
Формально - стандарт. А по факту :-(
SShtole
26.10.2021 15:16+27А по теме: мне давно кажется, что в индексе TIOBE на четвёртом месте находится не C++, как там написано, а совершенно другой язык — C/C++. Допустим, мне понадобился SQL-движок с максимально шустрыми кастомными агрегирующими функциями. Я беру SQLite. При этом, в обвязочном коде используются и классы, и шаблоны, и ещё кой-чего, и в целом получается, что модуль написан на C++. Но на самом деле меня интересовал способ воспользоваться чисто сишной библиотекой, а эту архитектурную астронавтику из chrono оставьте себе, спасибо.
Ещё мне кажется, C++-ные пуристы со своими наездами не понимают, до какой степени их язык паразитирует на сишном фундаменте. Убери его — и язык из топа быстро скатится на дно рейтинга. Рядом есть другие языки и у них стандартные библиотеки не такие упоротые.
shybovycha
26.10.2021 16:04+26Я настолько еретик, что пишу в резюме C/C++/C#
atd
26.10.2021 22:25У меня есть пара проектов, где в C# unsafe ставится целиком на классы, причём почти на все. Вот там C/C++/C# вполне валидная категория )))
AquariusStar
26.10.2021 23:09А я наоборот. Пытаюсь засунуть привычные мне реализации ООП C# в язык Си. В основном, это работа со строками. C# unsafe я тоже вполне применял в одном проекте.
apro
26.10.2021 16:04+2Используете C-style массивы вместо std::array;
А это как? Ведь передача `int a[]`, `int a[20]` и даже `int a[static 20]` это всего
лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?
Используете функции из stdio.h вместо ... потоков ввода-вывода
Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов. А умение clang/gcc проверять строчку форматирования во время компиляции сводило преимущество в типизации к нулю. Что-то изменилось?
F0iL Автор
26.10.2021 16:16+2А это как? Ведь передача `int a[]`, `int a[20]` и даже `int a[static 20]` это всего лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?
А никто ничего и не путаетю Простой пример: у вас есть какая-то структура, внутри которой вы хотите хранить статический массив. Ну или просто у вас есть массив сам по себе, который вы где-то используете. std::array<int, 10> в памяти будет выглядеть точно так же, как и int[10], но в отличие от него 1) может быть передан/возвращен не только по ссылке/указателю, но и по значению; семантика точно такая же как и у остальных плюсовых контейнеров, хотите передавать по ссылке - передавайте 2) имеет готовый метод size(), что позволяет избежать классических ошибок с sizeof 3) умеет в итераторы, то есть совместим с range-based for и с алгоритмами из <algoritm>
lepota
27.10.2021 09:373) умеет в итераторы, то есть совместим с range-based for и с алгоритмами из <algoritm>
Обычный массив разве не умеет и не совместим?
int t[20]; for (auto &item : t) std::cin >> item; std::cout << *std::max_element(std::begin(t), std::end(t));
F0iL Автор
27.10.2021 09:42Да, здесь сработает, вариант с ручным std::begin/std::end я не учел.
А вот если вам передали массив просто как указатель, например int*, как это любят делать в сишном коде, то все становится гораздо сложнее :)
DistortNeo
26.10.2021 16:51+3А это как? Ведь передача
int a[]
,int a[20]
и дажеint a[static 20]
это всего лишь передача указателя, как можно передачу указателя перепутать с передачей самого массива?Не совсем. Можно делать так:
template <class T, size_t N> void Test(const std::array<T, N>& arr); template <class T, size_t N> void Test2(const T (&arr)[N]);
Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов.
Дело даже не в тормозах, а в такой неприятной штуке, как глобальное состояние, которое приводит к широкому спектру ошибок:
https://stackoverflow.com/questions/2273330/restore-the-state-of-stdcout-after-manipulating-itНу и достаточно уникальная реализация потоков в C++. Вызов
printf
хорош своей универсальностью: во многих языках он работает одинаково. Альтернативаprintf
— позиционные аргументы (типаprint "i = {i}, j = {j}"
). А вот аналога C++ потоков нигде нет.
0xd34df00d
26.10.2021 19:58+3Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов.
В моей практике плюсовые потоки таки были быстрее, так как строчку форматирования не нужно парсить каждый раз, если вы хотите прочитать условный миллион записей из файла.
Впрочем, какой-нибудь буст.спирит вообще всех рвет.
JaroslavTavgen
26.10.2021 16:17-6опытный плюсовик-затейник, который будет активно внедрять best practices
Переход на Java, Python или Javascript?
F0iL Автор
26.10.2021 16:17Для embedded-разработки, ага. Добавьте еще Golang и C# в список.
JaroslavTavgen
26.10.2021 16:31Ну если память не экономим и скорость не оптимизируем (или оптимизируем?), то можно хоть PHP, нет?
alexeishch
26.10.2021 17:48+2C# вот отлично работает в embedded. Пробовал на плате с 4ядерным ARM процессором и 2 Гб оперативы. Нужно было сделать MVP, который потом подрядчик не сильно улучшил переписав на C++. Работать стало конечно быстрее, но конечный потребитель разницы не заметил.
F0iL Автор
26.10.2021 18:16+2Ну тут все зависит от целевой платформы. Когда у вас какой-нибудь STM8, то придется ужиматься по-полной, когда у вас 4 ядра мощных и 2 ГБ оперативы, тогда можно развлекаться как душа пожелает, а когда у вас 500 мегагерц и 256 мегабайт, то шикануть уже не получиться, но и каждый байт и каждый такт считать не обязательно.
Gordon01
27.10.2021 11:52+2а когда у вас 500 мегагерц и 256 мегабайт, то шикануть уже не получиться
У меня когда-то был компьютер с amd k6-2/500 и 128 мегабайтами памяти. Так вот там был сайт на пхп с mysql, еще кучка сервисов, которые обслуживали около сотни человек.
Современный ПХП еще в несколько раз быстрее той пятой версии, что я тогда использовал.
Как-то вы совсем недооцениваете возможности железа.
Areso
28.10.2021 02:50+1Только за продшие 20 лет не только интерпретаторы и компиляторы стали эффективнее, но и прикладной софт разбух до неимоверных масштабов.
Вы сегодня на 256 метрах ОЗУ вряд ли классический LAMP стек с какой-либо существенной полезной нагрузкой сможете развернуть.
Areso
27.10.2021 21:44+42 ГИГА ОЗУ в Эмбеддед.
Чтобы я так жил.
Да вы там Ноду, прости господи, ДжиЭс можете использовать.
А у меня 32 КиБ ОЗУ и лапки.
alexeishch
28.10.2021 01:53+1Хм, в моём мире Embedded - это встраиваемые решения. Типа банкомата, бортового компьютера и прочее, на них может работать линукс. 32 кб ОЗУ - это микроконтроллер
Areso
28.10.2021 02:52Ну да, тут видимо вопрос куда и с каким бюджетом денег и энергопотребления встраиваемые. Вот у вас банкомат, а у меня сигнализация самопальная, и ее не хочется, как Эпл Вотчи, заряжать каждый день.
lromanov
26.10.2021 19:33+2Вот вы смеётесь, а существует и довольно популярен python для микроконтроллеров: MicroPython
victor_1212
26.10.2021 16:48+5>опытный плюсовик-затейник
как-то не пришлось таких встречать, приходилось видеть:
либо опытный плюсовик,
либо плюсовик-затейник,
либо опытный затейник,
но это конечно не значит, что такое животное не существует, просто пока не обнаружено :)
как обычно, imho
F0iL Автор
26.10.2021 16:50либо опытный плюсовик,
либо плюсовик-затейник,
либо опытный затейник,
Кстати, если собрать всех троих в одной команде, то может получиться отличное комбо!
victor_1212
26.10.2021 16:57Возможно Вы имеете в виду gumbo?
https://www.neworleans.com/restaurants/traditional-new-orleans-foods/gumbo/
olekl
26.10.2021 16:25+2Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение? Особенно применительно к микроконтроллерам и штукам класса зарядка для электромобиля? Где гораздо уместнее смотрелось бы RTOS+MISRA C хотя бы...
F0iL Автор
26.10.2021 16:39+2MISRA есть не только для C, но и для C++, и многие правила из нее мы тоже используем.
В самой зарядке используется RTOS, а вот в коммуникационном шлюзе для группы зарядных станций уже крутится Embedded Linux. Учитывая, что этот шлюз должен уметь в OCPP (JSON поверх Websockets с TLS и авторизацией по сертификатам), MQTT (тоже поверх Websoсkets с TLS), Modbus, BACnet, NTP, всё это с IPv6, а в списке требований к смежным проектам (которые появились еще раньше) на той же платформе есть еще RestAPI с Redfish, Avahi, SNMP, авторизация по LDAP и Radius с ролевой моделью, remote syslog, SSH CLI, кастомные протоколы на базе protobuf, а бонусом еще и сертификация по UL-2900-2-2, то Linux тут является оптимальным решением для достижения гибкости и минимизации велосипедостроения.
olekl
26.10.2021 16:58+2Если в самой зарядке RTOS тогда вопросов нет, коммуникационный узел на Linux как раз понятно, что будет оптимальным. Я просто попробовал представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.
ECRV
26.10.2021 18:17+7Наконец из вашего текущего комментария стала понятна простая вещь. Вы пишете под Linux. В область Embedded разработки входит еще и огромный пласт разработчиков под мк и их вы не убедите использовать C++ в своих проектах, ибо под всеми контейнерами уже не поймешь сколько у тебя памяти и на что ушло. Напишите в шапке или вначале статьи что вы имеете ввиду только Linux и половина хейта к вам отвалится автоматом. Под камень с MMU всем очевидно что нужно пользоваться всеми прелестями готовых плюсовых контейнеров.
F0iL Автор
26.10.2021 18:21+1Напишите в шапке или вначале статьи что вы имеете ввиду только Linux и половина хейта к вам отвалится автоматом.
Вообще-то об этом было изначально прямым текстом сказано в начале статьи, аж в третьем по счету предложении:
Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++
Не помогло.
Да и в принципе в C++ кроме контейнеров, строк и прочих вещей с неявной аллокацией есть также много полезных фич с нулевым оверхедом.
olekl
28.10.2021 17:00Я однажды заглядывал внутрь с++ фреймворка Mbed для STM32, когда оказалось, что изменение GPIO пина с Input на Output занимает на нем 600+ тактов... Всего-то несколько слоев классов, да.
allcreater
01.11.2021 13:17+1Ни разу именно эту штуку не видел и говорить предметно не могу, но рискну предположить, что не в классах и количестве их слоёв дело, скорее — в том, какие именно абстракции используются и сможет ли компилятор их оптимизировать или нет.
Идиоматичные обёртки на C++ могут быть вполне себе zero-cost, на хабре есть несколько статей на тему, вот одна из них.
С другой стороны, (не знаю, как сейчас, но как минимум несколько лет назад) STMовский HAL был весьма тормозящей либой, даром что сишной и даже написанной производителем железок.
Arcanum7
27.10.2021 10:12Вот это пояснение что, где и как используется очень не хватает в статье. Без этого пояснения статья выглядит как минимум тонким троллингом и слегка провокацией.
F0iL Автор
27.10.2021 11:16+4В статье нигде не сказано "пишите только на C++". И нигде не сказано "пишите под embedded только на C++". Так "что, где и как" у нас используется к статье особо отношения не имеет. Мысль другая: если под ваш проект и требования хорошо подходит C - пишите на C, если хорошо подходит C++ - пишите на C++. Но имейте в виду, что С и C++ - при кажущейся внешней схожести, языки очень разные, а попытка перепрыгнуть с одного на другой за пару дней приведет к тому, что вы будете писать не на C++, а на "C с классами". Многие почему-то это не понимают.
victor_1212
26.10.2021 17:10+1>Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение
встречается, но не часто, редко бывает оправдано, и дело не в количестве протоколов, которые к C++ прямого отношения как правило не имеют, а скорее в сложности взаимодействия большого количества процессов в реальном времени, типа система из сотни устройств координирует работу, приходилось участвовать, удовольствие ниже среднего,
как обычно imho
ps
>представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.
как насчет spacex в случае когда что-то пошло не так?
Laryx
26.10.2021 16:47+2Хм... Оказывается, я "матерый плюсовик". С большинством из пунктов я согласен, но с некоторыми конкретно хотел бы поспорить.
Реально я "олдскульный плюсовик". На "чистом С" почти не писал, только в студенчестве, в самом конце 80х. Основной опыт - середина 90х - конец 2009, подавляющее время работы писал на MSVC++, в то время с библиотекой MFC. Сейчас много нового, смотрю, но, изучать уже не буду... Вобще от программирования отошел...
Xadok
26.10.2021 17:08+6Похоже комментаторы выше рассматривали 20 пунктов как совет к действию, это же просто шаблон для определения си с классами. Поэтому можно согласиться с каждым пунктом, но при этом и использовать исключения из каждого. Статья полезная, но уж слишком много провокаций.
Arcanum7
27.10.2021 10:09Ощущение что это очень тонкий троллинг. И возможно даже опасный для юных умов что не начали толком баловаться с ++ но уже подумывают об этом.
realimba
26.10.2021 17:46+3В Quake3 от C++ только классы но хуже он не стал. Уже сто раз видел проЭкты с невероятными мета гуру C++ которые через пол-года убегают оставляя после себя "чукча писатель не читатель".
F0iL Автор
26.10.2021 18:10+4И каждый второй мнит себя Кармаком, хотя даже близко на него не тянет, ага.
Наркоманию закрученного C++ метапрограммирования в стиле Александреску я тоже не очень люблю, именно по причине вами обозначенной - там в написанном зачастую без бутылки разобраться не может почти никто, за исключением того, кто этот код написал. Но в статье речь совсем о другом.
Am0ralist
28.10.2021 11:50Тут блин в запросах или xsl через полгода уже бутылка требуется, даже когда сам писал)
Только подробное комментирование хотя бы того, зачем этот кусок здесь.
SShtole
31.10.2021 22:49-1В Quake3 от C++ только классы но хуже он не стал. Уже сто раз видел проЭкты с невероятными мета гуру C++ которые через пол-года убегают оставляя после себя «чукча писатель не читатель».
Мне кажется, Q3 это одновременно очень плохой и очень хороший пример.
Очень плохой он потому, что при мысли об исходниках Q3 на ум в первую очередь приходит Fast inverse square root — ведь именно там же он впервые широко засветился. Такие вещи Кармак и компания делали явно не от хорошей жизни — компьютеры в те времена были намного слабее нынешних. И, следовательно, даже тот копеечный оверхед, который даёт, например, стандартная библиотека, мог играть для них принципиальную роль. Даже сильно позже, намного ближе к нашим дням, я обращал внимание, что писатели движков косо поглядывают на нововведения именно из-за привычки считать такты. Что поделать — эти люди сами выбрали мученическую стезю )) Но для нас, для большинства, эта разница не должна играть большую роль, а значит оглядываться на игропром, тем более такой старый, не очень продуктивно. Жизнь заставит — и на ассемблере будешь программировать!
А хорошим его делает… скажем вежливо, напоминание, что ЯП, вообще-то — инструмент для создания практически полезных программ. Таких как Quake 3, да. Или ядро Линукса. Кто может — тот делает, а кто не может — учит нас «совершенному коду» и «модерновому сиплюсплюсу». Я помню, как в своё время прочитал широко известную в узких кругах книгу Джеффа Элджера. Такую, с чёрненькой обложкой. Вы должны помнить — умные указатели, гомоморфные иерархии, вот это всё. Конечно, ходил под впечатлением: какой вумный дядька! Такое тройное сальто с переворотом и поцелуем себя в задницу в верхней точке при прыжке через горящий обруч — это ж не каждый сделает! А сейчас я поискал 'jeff alger' и ничего толком не нашёл. Вики про него не знает. Что он вообще сделал-то? Книжку написал?
Но само по себе это ещё полбеды. Настоящая беда в том, что эти астронавты летают в настолько разреженных слоях атмосферы, что им оттуда нас, убогих, с нашими повседневными проблемами, очень плохо видно. Процитирую я основоположника, который поставил C++ на те рельсы, по которым он теперь и катится — Александра свет Александровича. Учёного, как рекомендует его Википедия.Я уверен, что ООП методологически неверна. Она начинает с построения классов. Это как если бы математики начинали бы с аксиом. Но реально никто не начинает с аксиом, все начинают с доказательств. Только когда найден набор подходящих доказательств, лишь тогда на этой основе выводится аксиома. То есть в математике вы заканчиваете аксиомой. То же самое и с программированием: сначала вы должны начинать развивать алгоритмы, и только в конце этой работы приходите к тому, что вы в состоянии сформулировать четкие и непротиворечивые интерфейсы.
Вот так! Теперь вы знаете, почему горячо рекомендуемый в статье std::string ничего не умеет, а алгоритмы навешиваются на него как амбарный замок. Александра Александровича нимало не смущает тот факт, что типичный программист вообще редко пишет алгоритмы (в том смысле, который вкладывает он в это слово) — типичный программист ими в основном пользуется. И что старый, недобрый, пропахший нафталином CString действительно облегчал программисту жизнь при переходе от char*: он, во-первых, худо-бедно, реализовывал абстракцию «строка». Можно было не думать, Юникод там внутри или ещё что-то. (Да, макросы _UNICODE — это, наверно, не очень хорошо. Но это решение! А наличие ДВУХ базовых типов строк — это просто отказ от всяких попыток решить проблему). Во-вторых, он умел весь джентльменский набор по работе с текстом — поиски с обоих концов, замены, капитализации и прочее — прямо внутри класса. Может быть, кто-то думает, что мы таскаем классы просто по факту наличия? Нет. Класс должен приносить пользу. И, в-третьих, что немаловажно, создатели подумали о совместимости с Си. То есть, о том факте, что как на сиплюсплюсе не пиши, а рано или поздно придётся вызывать WinAPI (или API другой ОС). И заложили в него operator LPCTSTR(). Вызов `::CreateWindow(strClassName, strWindowName...` однозначно считывается как `:: СоздатьОкно(оконныйКласс, заголовокОкна...` Напротив, степановское детище заставляет писать так: `::CreateWindow(strClassName.c_str(), strWindowName.c_str()...`. Что в переводе на русский означает: `:: СоздатьОкно(контейнерСодержащийОконныйКласс.извлекаем_строку(), контейнерСодержащийЗаголовокОкна.извлекаем_строку()...`. Конечно, это сделано в интересах трудящихся. Нас же хлебом не корми, дай лишний метод вызвать руками, чтобы напомнить себе, что мы тут не в бирюльки играем, а пишем на взрослом языке. Народ и начал разбегаться, как только стало куда — Java, C#, you name it. Осталась кучка самых стойких, из которых половина просто привязана к сишным API, и воленс-неволенс вынуждена досмотреть этот цирк до конца — так и до тех доковырялись. Плохо, дескать, летаем. Низэнько.
W_Lander
26.10.2021 22:35+1Вы собираете свои emb проекты на ядре linux, написанного на С. Т.е. могу предположить, что как и в большинстве подобных проектов это уже 80% кода. Туда еще можно положить немаленький u-boot или его альтернативу. А потом заявляете, что мы вот, без обид образно говоря ), по CAN гоняем 10 кодограмм и десяток алгоритмов бизнес-логики крайне мемори и типо безопасно т.к. не контрибьютим ни строчки "warning: use of old-style". Так у вас де-факто 90% кода уже в опасности ). Вам надо срочно переписывать ядро! По крайней мере загрузчик уж точно можете написать, он же проще пареной репы.
F0iL Автор
26.10.2021 23:19Зачем? Человеко-лет на переписывание ядра потребуется слишком много, игра не стоит свеч. Пока что и ядро и загрузчик вполне себе удовлетворяют всем требованиям.
Хорошими сишными библиотеками тоже не брезгуем, особенно если для них есть врапперы.
Viktordp
26.10.2021 23:10Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++
Есть Cortex M4. 144 мгц и 160 к ОЗУ на борту. Необходимо поддерживать GSM модуль, FM микросхему. С FM Постоянно мониторить звук с эфира на предмет выловить кодовую посылку DTMF. В это же время обслужить микросхему Ethernet. При необходимости обслужить флеш память. По двум, трем каналам АЦП нужно мониторить звук на предмет поступления кодовой посылки DTMF. По команде транслировать звук из выбранного канала на УНЧ, или на выбранный выходной канал. Звук для обеспечения приемлего качества должен быть хотябы 24к семплов в сек.. Также необходимо мониторить солнечную панель и заряд аккумуляторов. И все это работает в режиме реального времени 24 часа в сутки. То о каком Ebedded Linux Здесь может быть речь? Только RTOS. Динамическая память не получается, так как некогда собирать мусор в памяти.
F0iL Автор
26.10.2021 23:13+1А причем здесь это-то? :) Для вашего конкретного проекта Linux не подходит, вы используете RTOS. А есть немало embedded проектов и устройств, где наоборот Linux вписывается очень хорошо, позволяя достичь гибкости и минимизировать велосипедостроение. Например тот, про который идёт речь во введении этой статьи.
А в C++ кроме контейнеров, строк и прочих вещей с неявной аллокацией есть также много полезных фич без динамических аллокаций и нулевым оверхедом, помогающих избежать ошибок и экономящих время программиста.
F0iL Автор
26.10.2021 23:28+1Динамическая память не получается, так как некогда собирать мусор в памяти.
Динамическое выделение памяти не обязательно связано со сборкой мусора, там вполне может быть RAII и reference counting.
В RTOS нередко используют блочные аллокаторы и пулы (например, в ThreadX и FreeRTOS), но вообще при правильном подходе даже с использованием классических аллокаторов можно не бояться временных затрат на выделение/освобождение в куче и не пугаться фрагментации, подобающим образом реализованные
malloc()
иfree()
выполняются за постоянное время и даже с предсказуемой фрагментацией кучи.
altervision
26.10.2021 23:26+15Как различить C и C++-разработчиков по их коду? Элементарно. У одного код написан на С, у другого на С++. С уважением, Капитан Очевидность.
F0iL Автор
26.10.2021 23:36-3Учитывая, что формально C++ является надмножеством C (за исключением пары моментов), то вопрос определения не такой уж и очевидный. Иначе бы не было этой статьи :)
NN1
27.10.2021 00:03+2F0iL Автор
27.10.2021 09:22Я бы сказал, об ANSI C++ 90-х годов и C89. Тогда там действительно количество "моментов" было в районе пары пары, если не считать те из них что уже тогда были анахронизмом даже в самом Си (типа K&R function definitions).
Естественно, потом пути языков начали расходиться сильнее (особенно после выхода C11 с VLA и restrict), но тем не менее и по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациями.
Xadok
28.10.2021 14:04+1по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациям
Тут вы можете сильно ошибаться. Что разрешено в С, часто включается в С++ по умолчанию в msvc или через gnu-extensions, но валидным С++ кодом не является, отсюда получается то, что в Сях ок, в плюсах - UB. Оно скомпилируется да, но работоспособность ничем не гарантирована.
Daddy_Cool
26.10.2021 23:55+1Вчера как раз размышлял на подобную тему, и даже придумал термин — «ассемблерное мышление». Т.е. изучаем элементарные операции, а дальше их комбинируя создаем всё что нужно. На курсах по асму нам объяснили ввод, вывод, арифметику. Преподаватель говорит — «Ну вот теперь вы можете написать Тетрис или Ксоникс». ИМХО, проблема изучения языков относительно высокого уровня (как С++), что когда знаешь Си, непонятно зачем нужны все эти плюш-плюшки. Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл. Видимо надо начать писать что-то более сложное, чтобы возникла необходимость и я увидел как что-то легко и изящно делается на ++ и долго и муторно на чистом Си.
Aldrog
27.10.2021 18:06Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл
Забавное описание. Я вот тем же самым занимаюсь, разве что в «боевой» конфигурации обычно чтение идёт потоком с камер, и результаты передаются какой-нибудь управляющей системе по какому-нибудь специальному интерфейсу. Но этот ввод-вывод совсем незначительную часть кода занимает.
bamovetz
27.10.2021 20:47+2Могу ответить вам на этот вопрос. Необходимость плюсов по сравнению с pure С начинаешь понимать когда проект становиться больше определенного размера, когда его нужно поддерживать и развивать.
Я преписывал код С на плюсы несколько раз именно из за того что при малейшей модификации вылазило куча проблем. Классический пример это освобождение памяти, когда на плюсах, при условии выполнения нескольких правил, можно вообще не задумываться об освобождении памяти при выходе из блока. А вот в С надо обязательно отследить все динамические переменные и их освободить. А когда внутри кода несколько ветвлений то освобождение надо делать в нескольких местах либо усложнять код чтобы все сводилось в одну точку.
yeputons
27.10.2021 22:35либо усложнять код чтобы все сводилось в одну точку.
А
goto
в нужную точку в хвосте функции для освобождения не помогает, как в ядре Linux часто делают? Или ресурсы часто не так линейно вкладываются?
warlock13
27.10.2021 00:13Я не великий гуру C++, но стараюсь по мере сил. И мне список понравился. У меня лично есть некоторые вопросы к пункту 10* и очень большое сомнение в пункте 16: всё-таки неймспейсы в C++ - это совсем-совсем не то же самое, что неймспейсы в C# или Java. Они созданы для решения конкретной проблемы (конфиликт имён при линковке и сопредельных ситуациях), не предназначены для структурирования кода и плохи в этой роли. В общем, моё текущее мнение (оно может поменяться в будущем с новым опытом): пункт 16 неверен не в каких-то отдельных случаях, а прямо совсем, категорически.
* даже в том же Rust я обнаруживаю, что очень часто
fn
(Rust-аналог сырого указателя на функцию) предпочтительнееFn
(Rust-аналогstd::function
), и я говорю не об интеропе.AnthonyMikh
27.10.2021 22:13* даже в том же Rust я обнаруживаю, что очень часто fn (Rust-аналог сырого указателя на функцию) предпочтительнее Fn (Rust-аналог std::function), и я говорю не об интеропе.
Вы что-то путаете, причём капитально.
fn
— это всегда функциональный указатель и всегда размером сusize
. А вотFn{, Mut, Once}
— это трейты, и их использование совершенно не обязательно подразумевает аллокацию памяти. Вдобавок, каждая конкретная функция в Rust имеет свой собственный тип нулевого размера, и использование обобщённых типов сFn*
-ограничениями позволяет сохранить нулевой размер в памяти, в отличие от функциональных указателей:struct FnWrapper<F: Fn()>(F); fn f() {} fn main() { use std::mem::size_of_val; assert_eq!(size_of_val(&FnWrapper(f)), 0); assert_ne!(size_of_val(&FnWrapper(f as fn())), 0); }
warlock13
28.10.2021 04:11Я ничего не путаю, я имею в виду разумеется `Box<dyn Fn...>`, и это по-моему очевидно из контекста.
IGR2014
27.10.2021 00:54+6Зачем такой спор в комментариях? Элементарно - пишите на C - вы молодец, оставьте его себе и пользуйтесь на здоровье.
Пишете на C++ - вы тоже молодец, но будет добры, соглашайтесь со всеми правилами языка! Никаких old-style кастов, никакого ручного управления памятью, используйте всё что уже есть в языке грамотно и с умом. И чёрт побери, не забывайте о кросс-платформенности! Код должен быть переносимым ради ваших будущих коллег!
YuriPanchul
27.10.2021 03:10+2Я это прочитал и подумал: боже, как хорошо, что я в отношении С/С++ остановился на стандарте в районе 1994 года (то бишь после templates но до exceptions), и вообще что сейчас проектирую хардвер на Verilog-е, а не пишу на том ужасе, в который превратился C++.
(При том что я был в прошлом автором продаваемого компилятора с C/C++)
F0iL Автор
27.10.2021 10:30+6Тут каждому свое. Я бы наоборот, в проекте, где используется C++ старше C++11 согласился бы работать только за двойную-тройную оплату, и то очень хорошо подумав перед этим :)
DoubleBY
27.10.2021 09:03+1Весьма странная статья. И странные комментарии. Мягкие. Не по мощам елей. На мой взгляд, статья граничит с признанием автора в профнепригодности. Кто то тут способен спутать процедурного и объектного программера? Цирк ёёёё... Не, я конечно понимаю, круг ваших задач ограничен рабочим окружением, но все таки... Ограниченность задач играет с вами дурную шутку дружище. Сложность низкая, объемы кода видимо не высокие, всегда можно прошивку с нуля переписать и не нужно париться с кодом доставшимся в наследство))). А вы возьмите и создайте два варианта даже простенькой игры с GUI на С и на С++ а потом передайте своему коллеге с приколом внести в программу новую фишку. И после этого уже наверное можно вудет с вами обсуждать похожести языков. И все таки настолько не интересоваться программированием, что бы не выйти за рамки рабочей рутины, и не попытаться сделать что то еще, не попытаться получить другой опыт? Я сочувствую той организации где работает автор. Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот. И эти философии у людей закладываются годами. И именно с инерцией изменения философии человеческой связана сложность освоения новой парадигмы, а не с новым языком. Новый синтаксис ты можешь пробежать глазами и начать использовать за весьма короткое время, но создавать продукт промышленного качества... Оооо - Нет. Как показывает практика, подавляющее большинство работничков не достигает уровня понимания достаточного для самостоятельной работы в принципе. Они так и остаются на уровне кодеров, которым лиды нарезают задачи и контролируют каждый их шаг. Увы.
F0iL Автор
27.10.2021 10:05+2Я прочитал ваш комментарий, и, честно сказать, даже растерялся. Сначала даже засомневался, точно ли вы его оставили к этой статье, а не к какой-то другой. Потому что вы берете что-то, чего я даже близко не говорил и спорите с этим, применяя ровно те же самые доводы, что я излагал в статье :)
Кто то тут способен спутать процедурного и объектного программера?
Я в статье как раз говорил, что если взять Си, добавить в него ООП и начать писать в объектном стиле, то у вас получится не C++, а Си с классами.
Сложность низкая, объемы кода видимо не высокие, всегда можно прошивку с нуля переписать и не нужно париться с кодом доставшимся в наследство))
Ради интереса, запустил cloc в дире с проектом (специально без внешних зависимостей и 3rd-party библиотек), получилось такое:
github.com/AlDanial/cloc v 1.72 T=18.71 s (282.3 files/s, 42135.6 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- C++ 1777 45598 57553 250059 C/C++ Header 1961 34941 66222 137464 C 76 3402 9709 40005 JSON 123 38 0 23530 CMake 632 3942 3311 22037 Python 138 4386 8544 18588 Bourne Shell 308 3304 3326 14073 Markdown 46 2959 0 11168 make 32 3036 1901 5730 XML 75 185 211 3730 YAML 12 133 59 2913 DOS Batch 54 456 266 2148 ECPP 22 174 38 1191 JavaScript 4 67 30 673 Ruby 6 107 135 310 CSS 3 60 24 297 m4 1 0 0 156 HTML 2 3 10 56 INI 9 4 9 50 ------------------------------------------------------------------------------- SUM: 5281 102795 151348 534178 -------------------------------------------------------------------------------
Считать ли это "большим и сложным" - ну не знаю, не люблю меряться пиписьками.
и не нужно париться с кодом доставшимся в наследство
Сейчас - к счастью да, но вообще, такой опыт был. В прошлом проекте приходилось даже копаться в коде, который чуть ли не старше меня, всего пары лет не хватило :)
И все таки настолько не интересоваться программированием, что бы не выйти за рамки рабочей рутины, и не попытаться сделать что то еще, не попытаться получить другой опыт?
Программированием интересуюсь еще с 13 лет, и вообще-то я всю свою сознательную жизнь и карьеру только и делаю, что выхожу за рамки рабочей рутины и пытаюсь получить другой опыт. Как в плане самих проектов на работе: в разное время занимался и разработкой веб-порталов, и программированием автоматики для нефтедобычи, разработкой диспетчерского контроля для аэропортов, разработкой браузеров (в том числе контрибьютил в Chromium), разработкой коммуникационных шлюзов. Из языков - C, C++, C#, JS. В свободное время иногда контрибьючу в разные open source проекты, пилю утилитки для себя, в том числе на языках, с которыми не работаю в основное время, таких как Go и Rust.
Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот.
В точку. Именно об этом эта статья, и именно эту мысль я несколько раз в разных формах в этой статье и повторял.
Новый синтаксис ты можешь пробежать глазами и начать использовать за весьма короткое время, но создавать продукт промышленного качества... Оооо - Нет.
Все верно. И еще раз: именно об этом эта статья, и именно эту мысль я несколько раз в разных формах в этой статье и повторял.
DoubleBY
27.10.2021 13:02Возможно я просто чего то не понял. Без обид. Я тоже лох порядочный и не претендую на что то великое. Но. Ты возьми дезассемблированный код, или посмотри машинные коды в PE. что ты там увидишь? Объекты? Процедуры? Не будет их там. Там будут пушенья параметров в стэк и условные/безусловные переходы к адресам. Поэтому, все костыли в виде языков программирования это для нас, для людей. Еще раз повторюсь. У нас разночтение возникло из за разных объемов и наследия. Ничего удивительного нет, что С++ похож на С. С++ выродился, мучительно и тяжело выломался из Си под воздействием обстоятельств, а именно - под давлением возрастающей сложности создаваемых систем. Когда у тебя проект объемом в пол года - год работы коллектива, и ты не имеешь права на ошибку, приходят совсем другие требования. Просто я с Си начинал развлекаться еще в девяностых, потом был С++, потом С#. И в моей голове объектный и процедурный стили никак не смешиваются даже теоретически, как масло и вода, несмотря на обманчивую схожесть синтаксиса и наличие функций/процедур. Я уже давно не смотрю на свои поделки глазами кодера. Как правило, я сначала достаточно долго хожу вокруг новой поделки ковыряяся в носу (I pick in a deep in my big nose), и поковырявшись исторгаю из себя зачатки жизнеспособной архитектуры, обычно это DDD. И тут я никак не могу допустить, что можно коим то образом я могу недопонять где у меня будет линейное решение, а где нет.
klirichek
28.10.2021 06:35+1"Пушенья и переходы по адресам" это всё же не про парадигму, а про компилятор. Ну и про известную идиому (применимую ко всем подобным языкам) о том, что преждевременная оптимизация - зло. Да, всё превращается в машинные коды конкретного процессора. Но изначально всё идёт из выразительности языка и других плюшек. И хорошо, когда для перехода от объектов к процедурам _не нужно_ менять язык, а можно просто писать в другой парадигме на том же языке.
А вот, например, использование COM (неважно, на С или С++) - в дизассемблере вполне видимо поверх всех "пушений и переходов" (а если есть ещё и отладочные символы - то это вообще очень сильно помогает в reverse engineering). Так что в этом смысле в PE вполне можно увидеть и процедуры, и объекты.
Kotofay
29.10.2021 23:28Мне кажется, что на С объёмные проекты вполне удаются. Например ядро Линукс.
Ну или Windows на худой конец.
Antervis
27.10.2021 10:05+2Кто то тут способен спутать процедурного и объектного программера?
вы наверно не слышали про то, как на плюсах пишут в процедурном стиле или как на си пишут в объектно-ориентированном (пример)? Очевидно что использовать ООП-парадигму в плюсах сильно удобнее чем в си, но и любую другую тоже. В общем, давайте поскромнее со всеми вашими профнепригодностью, ограниченностью, уровнем кодера...
bamovetz
27.10.2021 20:57+2Вот в этом и проблема. Для использования ООП парадигмы надо таки иметь поддержку языка иначе будет очень больно.
Я считаю что использование ООП в pure C это ересь и то что например нагородили в Gnome это ужас. Такое получилось только потому что чистые С-программисты решили доказать миру что на С вполне пишется большая GUI-система. Все что там нагромождено в ООП-стиле - на порядки удобнее и проще и главное безопаснее делать в С++.
klirichek
28.10.2021 06:04+1Кто то тут способен спутать процедурного и объектного программера?
...
Они созданы для работы в разных парадигмах и для решения разных задач
Так вы не языки сравниваете, а парадигмы. Можно вполне писать на плюсах в процедурном стиле, и это таки будут плюсы, а не си. Он (С++) мультипарадигменный, всё стерпит. Если про С ещё можно сказать, что там сложно в ООП, то вот говорить, что на С++ нельзя в процедуры или даже ФП - уже звучит странно.
Arcanum7
27.10.2021 10:06Умножаемые комментаторы, а вот такой вопрос:
есть чистый сишный проект, надо сделать что-то где без классов(т.е. без ++) никак от слова совсем.Насколько "глубоким" проникновение ++ в тело Си проекта допустимо?
Насколько будет правильно писать "до упора" на Си но когда уже никак - то ++, но по самой минималке насколько это оправдано?
dvserg
27.10.2021 10:26+2А либы не спасут? Основной проект на С, всякие расширения "где без классов никак" - в проект DLL/LIB. Мне кажется более правильным при необходимости до упора следует применять принцип совмещать, но не смешивать ( принцип Бонда ).
wkia
27.10.2021 11:40+1При желании, вы можете вынести необходимую функциональность в отдельную либу, написанную на с++, как предложил @dvserg, но здесь начинаются вопросы по ограничениям на линковку? Сможете ли вы линковать свой си-проект со стандартными либами с++ (статически или динамически), если понадобится?
Кроме этого может существовать проблема использования исключений. Будете ли вы их использовать или использовать классы стандартной библиотеки? На некоторых платформах, например, есть ограничения на использование исключений. Если у вас подобный случай, то вам нужно думать, как обрабатывать ошибки классов стандартной библиотеки.
lorc
28.10.2021 00:51В ядре линукса есть классы и объекты. На чистом С, да.
Может вам не так уж и нужен С++?
DummyBear
27.10.2021 10:58+4Я когда попробовал на с++ пособеседоваться вроде честно поменял все printf на cout <<, и даже насоздавал каких-то классов, но меня всё равно спалили и сказали, что пишу на си. :(
Так что даже выполнение пунктов из статьи недостаточно для качественной мимикрии под плюсовика, там вся парадигма отличается.
wkia
27.10.2021 11:23std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);
Очень много чего не подойдет, когда вы не можете работать с динамической памятью. Легче перечислить, что подойдет.
ECRV
27.10.2021 11:45+5Изначально я думал, что много хейта от того что задели Embedded программистов, как меня. Почитав больше комментариев я вижу что это не так. Задели программистов старой школы си.
Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день. Это прямо не говорится и для Автора очевидно он то знает плюсы, но не очевидно для старой школы. Я то как раз сам прифигел при переходе на новые плюсы (не относится к Qt), и пока не проникнешься - не поймешь.
Автору респект, ты двигаешься в правильном направлении и в правильном двигаешь остальных. Люди, которые вопят про "непрофпригодность" не делали проектов на новых плюсах с учетом всех твоих пунктов и всех нововведений, и им просто непонятно то что тебе уже очевидно. Им кажется, что ты готов выкинуть хорошего программиста на Си (коими все себя считают) только из-за того что "Плюсы лучше". И многие программисты на Си думают что и плюсы тоже самое. Когда-то так и было, но не сейчас, и отличия есть как минимум в ваших пунктах.
Статья хорошая, хоть и при первом прочтении у меня и вызвала негативные эмоции, ибо я тот самый Си-программист и мне обидно признавать, что я сделаю приложение под Linux за больший срок чем даже джун но на плюсах. К счастью, я их и не пишу, а плаваю как раз там где и нужен Си.
Еще раз лучи поддержки Автору
F0iL Автор
27.10.2021 12:58+3Спасибо. Вы все правильно поняли.
Я, на самом деле, немного недоумеваю. Такое ощущение, что многие комментаторы вообще не дочитывают публикацию до конца, а сразу же бросаются писать гневные комментарии, сливать карму (аж на -8 слили, лол) и минусовать статью. При том что я изначально обозначил, под какую платформу мы пишем и почему так, и во второй половине статьи отдлельно, прямым текстом и жирным шрифтом отметил, что всё описанное выше имеет свои границы применения и исключения, и все зависит от конкретной ситуации и конкретного проекта. Но люди почему-то этот абзац игнорируют полностью, начинают докапываться до частностей и даже почему-то называют утверждения в статье "безапелляционными", хотя все ровно наоборот :(
ECRV
27.10.2021 13:43+4Как мне кажется, программистов на Си сильно больше чем на плюсах, тем более чем хороших программистов на плюсах. И преимуществ у Си много, но они постепенно пропадают. Из реальных областей которые у нас, остались это: малоресурсные мк, низкое потребление или драйвера. Сейчас внутри копеечного мк esp32 памяти столько, что ее даже толком и не считают. За 10 баксов можно взять готовый одноплатник на Linux с более-менее отлаженными драйверами и впихнуть в него даже arm-контейнеры.
Время разработки проектов сильно сократилось за последние 10 лет. Помню пару лет назад читал статью на хабре где посыл был примерно такой:
"Друг попросил сделать освещение по хлопку. Я еб...лся с выбором мк, кое как уместил туда свой код так чтобы работало и выставил другу счет за разработку а он носом повертел. Больше не друг."
При том что уже тогда любой школьник мог просто взять arduino набор за 1000 рублей и все сделать за один вечер скачав библиотеку из интернета, автор той стать упорно все делал с нуля. Уже тогда было понятно что целый класс, невероятно полезных в свое время, людей превратился в набор староверов, мешающих прогрессу и даже теряющих друзей в угоду своей вере. И этот класс страдает от того что их, таких полезных людей, не берут на вакансии С++, хотя казалось бы, каких-то 10 лет назад вообще проблем никаких не было. Страдает, от того что сам понимает что мир шагнул вперед, а они остались на месте. Вы лишний раз напомнили нам об этом и получили вагон минусов.
Вот примерно так думаю я
Areso
27.10.2021 21:52у Arduino (особенно у дешевых типа Uno, LilyPad) ресурсов тоже как бы не сильно дофига.
Другое дело, что наколхозить на них относительно рабочее решение вполне реально за 1 выходной.
saterenko
27.10.2021 14:02-2Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день.
Только если говорить об абстрактных задачах, типа: "на голом языке получи из файла строки, отсортируй их и сохрани в другой файл", потому как в плюсах есть готовые структуры в std.
Если говорить о реальных проектах и о программистах с опытом и багажом библиотек, время будет сопоставимо, а может на C даже меньше, потому как проект собирается за 10 секунд, а не за минуту, потому как при отладке больше всего времени в плюсах уходит на сборку...
В больших проектах большую часть времени занимает не кодирование, а придумывание алгоритмов, отладка и покрытие тестами.
Я долго разрабатывал на C и у меня накопился некислый багаж как сторонних библиотек для решения задач, так и собственных решений под все нужды проектов в моей сфере (системы управления рекламой). И из этого конструктора я собираю решение не медленнее, чем на плюсах, на котором пишу проекты последние лет 5.
Кстати пробовал golang, прекрасный язык, красивый, куча стандартных и сторонних библиотек, но не получилось на нём разрабатывать быстрее, чем на C/C++.
ECRV
27.10.2021 14:47+2В моем комментарии я не старался охватить все аспекты и позволил себе использовать утрирование. Понятное дело что не каждый проект на плюсах можно сделать быстрее в 5 раз, или даже просто быстрее.
Условия, которые вы составили в своем сообщении можно перефразировать так: "У нас есть набор готовых проверенных решений и библиотек на Си, нам нужно сделать бизнес-логику и запустить в прод. На каком языке писать будем?"
Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.
Современные языки, в отличие от Си стараются включать в себя огромный набор стандартных библиотек и это позволяет разным людям работать в одной и той же кодовой базе, а так же быстро разбираться в чужих проектах. Вряд ли всем известно как работать с вашими библиотеками или готовыми решениями. Вы умрете и что делать бизнесу? Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.
Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел
saterenko
27.10.2021 15:18Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.
Если не брать новичков и собеседования, обычно программист работает в какой-то "своей" сфере: разработка для микроконтроллеров, gamedev, мобильные приложения и т.п. и работодатель покупает не знание языка и умение решать олимпиадные задачи, а опыт работы в конкретной сфере, а с опытом программист "обрастает" инструментами для решения задач в этой сфере.
Современные языки, в отличие от Си стараются включать в себя огромный набор стандартных библиотек и это позволяет разным людям работать в одной и той же кодовой базе, а так же быстро разбираться в чужих проектах. Вряд ли всем известно как работать с вашими библиотеками или готовыми решениями. Вы умрете и что делать бизнесу? Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.
Как показывает мой опыт, для новичков в сфере, к которым вы аппелируете выше ("абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение) фреймоврки значительно затормаживают вхождение в проект, потому как они весьма наворочены и требуют отдельного изучения фреймворка и его осбенностей. Одного языка знать недостаточно.
С другой стороны, хорошо написанный проект, например nginx, написанный на голом C, с собственной реализацией подавляюшего большинства алгоритмов (в том числе деревьев, hash-таблиц и т.п.), читается легко и непринуждённо... На понимание nginx на С мне потребовалось сильно меньше времени, чем на понимание проекта на PHP на Symfony.
Новичек облажается с вашими решениями и библиотеками из-за закона дырявых абстракций, но если бы вы пользовались преимущественно std, то вероятность провала была бы сильно ниже.
В реальном проекте новочёк облажается на уровне архитектуры, а не на уровне использования тех или иных библиотек. По библиотекам есть документация.
Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел
Я ничего не писал о своём подходе. Автор жалуется на то, что в плюсовые проекты приходят сишники и разрушают его приплюснутый "храм". Но дело тут не в языке, си или плюсах, дело тут в культуре человека, если он не уважает "чужое", он будет одинакого "говнокодить" как на си, так и на плюсах. Меня вот бесит, когда кто-то приходит в проект и начинает писать в своём стиле, а не в стиле, который устоялся в проекте...
ECRV
27.10.2021 15:55+1Ваше сообщение показывает, что мы с вами очень далеки от понимания друг друга и я не вижу смысла в дальнейшем обсуждении тут, не утверждая прав я или вы. Возможно кто-то следующий подхватит, продолжит и выяснит. Но я, из уважения к вашему небезразличию, оставлю в этом треде этот, последний, комментарий и займусь чем-нибудь еще
elektroschwein
28.10.2021 17:53+1А теперь смотрим, у Васи накопился один багаж библиотек и велосипедов для решения задач, у какого-нибудь тоже сишника Пети - совсем другой.
И когда Вася придет в проект, который писал Петя и ещё кто-нибудь, ему кроме понимания самой логики и специфики проекта в довесок ещё придется придется разбираться во всем том, что используют в этом проекте (и это не то, к чему привык он), а когда нужно будет вносить в проекте изменения, придется ещё заново натыкаться на все их подводные камни.
Так что нет, в этом плане чем больше унификации - тем лучше, особенно когда она не несёт ущерба чему-то другому. Да, в любом сколь-менее серьезном проекте без библиотек обойтись вообще не получится, но если есть возможность понизить порог вхождения и минимизировать количество потенциальных проблем и ошибок - надо этим пользоваться.
saterenko
29.10.2021 10:08Если вы строите стандартные дома-муравейники эконом-класса, тогда да, нужна максимальная унификаци. Если вы строите индивидуальный дом, под требования заказчика, унификации будет меньше.
Проекты бывают разные.
elektroschwein
29.10.2021 12:20+3А в данном случае это не важно. Вне зависимости от того, строите вы муравейники или индивидуальный дом, при прокладывании проводки вы все равно будете использовать провода, коробки и автоматы, и все равно следует соблюдать ПУЭ. При проектировании водоснабжения и отопления вы все равно будете использовать трубы, запорную арматуру, отсечные клапана, регуляторы. А вот "кулибиных", порящих в этом деле отсебятину, обычно через N лет матюками припоминают и заказчики, и коллеги. И лучше рисовать чертежи по ГОСТ/ЕСКД/ISO, а не изобретать каждому свои обозначения в документации. Точно так же в мире разработки ПО. Что вы клепаете какую-нибудь простую CRM на базе модного фреймворка, что пишете кастомную систему под специфичные требования заказчика и с хайлоадом - в основе все равно будут те же общеиспользуемые в CS алгоритмы, контейнеры, паттерны, подходы, протоколы. Если только у вас там не совсем что-то оооочень специфическое (например, высокочастотный трейдинг, где все на lock-free построено и хардкорные оптимизации), в чем я сильно сомневаюсь, да и это всё-таки в любом случае редкое явление. Поэтому, если у вас в коде есть утка, желательно чтобы выглядела как утка и крякала как утка.
saterenko
29.10.2021 12:46+2Ну так и используются стандартные конструкции языка и стандартные алгоритмы. Вопрос лишь в том, до какого уровня и зачем используются стандарты.
Если в одном, слабо нагруженном месте я использую std::unordered_map, потому как высокая производительность тут не нужна, то в другом, высоконагруженном месте, я использую JudyArray, потому как он даёт (или давал на момент разработки) более чем двухкратный выигрыш по производительности.Скажите, пожалуйста, JudyArray -- это стандарт или осебятина от "кулибина"? Если CoCo Hash на моих данных работае лучше контейнеров из std и я его использую -- это стандарт или отсебятина?
Если мне, на каждый запрос надо генерировать uuid и стандартная библиотека это делает это медленно и я пишу собственную, это лютая остебятена и такой код
uuid_t uuid; uuid_generate_random(uuid); char cuuid[37]; uuid_unparse_lower(uuid, cuuid);
исключительно лучше понимается проффесионалом, чем такой
rapiduuid::Value uuid; rapiduuid::Parser parser; parser.generate(uuid); std::string s = parser.toString(uuid);
Если только у вас там не совсем что-то оооочень специфическое (например, высокочастотный трейдинг, где все на lock-free построено и хардкорные оптимизации), в чем я сильно сомневаюсь, да и это всё-таки в любом случае редкое явление.
Зря сомневаетесь, не трейдин, но тоже весьма нагружено и по-возможности блокировки не используются.
Проекты разные бывают и нельзя все под одну гребёнку...
Я дорабатывал проект на Java, который писал проффесионал, у которого были все возможные сертификаты по Java, но тупило местали люто.
Управлял проектами на "стандартных" Django, Symfony, где тоже было всё стандартно, но трудно для понимания и оптимизации другими программистами, с хорошим опытом в этих фреймворках.
Были проекты от "кулибиных" с отсебятиной, но читались они как хороший роман. Да вон nginx сплошь отсебятина от "Кулибина".
Не использование стандартов и знание паттернов делает проект хорошим, работающим, понятным.
eao197
30.10.2021 07:46+1исключительно лучше понимается проффесионалом, чем такой
Можно задать пару-тройку вопросов, которые сходу возникли у (вроде как профессионала) при первом взгляде на этот фрагмент?
char cuuid[37]; // Почему именно 37? // Что будет, если написать 36? // Нижеследующий вызов как-то проверяет // размерность cuuid? Если нет, то почему? uuid_unparse_lower(uuid, cuuid); // Что значит "unparse"? // Почему не "format_to_string_lower"? // Откуда видно, что результат unparse (что // бы это ни значило) помещается в cuuid?
saterenko
30.10.2021 09:00Вроде как профессионал может ввести в гугле "uuid_unparse_lower" и понять по первому результату, что это "стандартная" библиотека для генерации uuid в linux и вопрос надо адресовывать авторам библиотеки, а не мне. Не к тому докопались ))
Ну и опять таки, если зайти по этой первой ссылке, то в первом же предложении секции DESCRIPTION есть ответ на ваш вопрос
The uuid_unparse() function converts the supplied UUID uu from the binary representation into a 36-byte string (plus trailing '\0') of the form 1b4e28ba-2fa1-11d2-883f-0016d3cca427 and stores this value in the character string pointed to by out
eao197
30.10.2021 09:17+2Вы утверждали, что у профессионала вопросов по коду не возникнут. Вот, возникли. По части из них вы отправляете искать ответы в Интернете, на часть из них не ответили вообще.
Так что приведенный вами код вызывает кучу вопросов, на которые нужно a) тщательно собирать информацию и b) скрупулезно следить за тем, что пишешь, ибо если по недосмотру объявишь cuuid[36], а не cuuid[37], то можно и ногу отстрелить.
C++ в этом плане не сильно далеко ушел, но в нем хотя бы можно от части проблем защититься. В частности, можно сделать совсем тонкую обертку вокруг uuid_unparse:
class uuid_as_cstr { std::array<char, 37> value_{0}; public: uuid_as_cstr() = default; [[nodiscard]] const char * c_str() const noexcept { return value_.data(); } [[nodiscard]] char * data() noexcept { return value_.data(); } }; [[nodiscard]] uuid_as_cstr uuid_to_c_str_lowercase(const uuid_t & uuid) { uuid_as_cstr result; uuid_unparse_lower(uuid, result.data()); return result; } ... const auto cuuid = uuid_to_c_str_lowercase(uuid);
И вы не будете иметь проблем ни с размерностями, ни с тем, что можете случайно начать использовать cuuid до присвоения ему значения.
saterenko
30.10.2021 09:36Вы задаёте вопросы по стандартной библиотеке для решения задачи генерации случайного uuid, которая была приведена в качестве примера, в ответ на утверждение, что стандартное читается проще. Мне странно, что вы от меня ждёте ответов на вопросы, которые должны быть адресованы авторам библиотеки, а не мне. Но ваша реакция говорит о том, что моя "отсебятина" читается лучше, чем стандарт...
Вместо использования 4 строк стандартного кода вы предлагаете написать обёртку вокруг стандартной библиотеки, это должно как-то упростить понимание кода???
Если вы хотели сказать, что в вашем случае uuid получатся одной строкой, а не 4-мя, то в у меня он тоже обёрнут в функцию:
std::string Utils::makeUUID() { uuid_t uuid; uuid_generate_random(uuid); char cuuid[37]; uuid_unparse_lower(uuid, cuuid); return std::string(cuuid); }
eao197
30.10.2021 09:46+4Вы задаёте вопросы по стандартной библиотеке для решения задачи генерации случайного uuid
Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.
Мы здесь даже не будем углубляться в выяснение того, в каком именно стандарте определена uuip_unparse и почему она считается "стандартной библиотекой".
Но зато мы можем легко продемонстрировать то, что происходит в реальной жизни: я прихожу в проект, который раньше писался условным Васей Пупкиным, и в первый раз вижу код, вроде показанного вами. И тут у меня возникают вопросы, которые я озвучил. И на которые вы смогли лишь ответить "ищите инфу в интернете". Так ведь это и есть показатель того, что код не так прост в понимании, как может показаться.
Вместо использования 4 строк стандартного кода вы предлагаете написать обёртку вокруг стандартной библиотеки, это должно как-то упростить понимание кода???
Это может выглядеть как парадокс, но тем не менее именно так и происходит зачастую.
Если вы хотели сказать, что в вашем случае uuid получатся одной строкой, а не 4-мя, то в у меня он тоже обёрнут в функцию:
К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?
saterenko
30.10.2021 10:09Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.
Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.
Но зато мы можем легко продемонстрировать то, что происходит в реальной жизни: я прихожу в проект, который раньше писался условным Васей Пупкиным, и в первый раз вижу код, вроде показанного вами.
Вы его, скорее всего, не увидите, вы увидете что-то типа
id_ = Utils::makeUUID();
и у вас вряд ли возникнут вопросы, что тут происходит и есть ли проблемы в реализации, коль эта функция вызывается десятки миллиардов раз в сутки и за годы проблем не возникало.
Это может выглядеть как парадокс, но тем не менее именно так и происходит зачастую.
Ну каждый верит во что хочет, да.
К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?
К любой функции можно при желании докапываться до бесконечности. Конкретно в этом случае временем на аллокацию можно принебречь, потому как в цикле обработки запроса производится на многие порядки больше работы, чем аллокация 37 байт памяти...
Для того места, где это действительно важно, как я писал выше, используется собственная функция генерации случайного uuid, написанная с использованием intrinsic-ов и работающая на 2 порядка быстрее стандартной.
eao197
30.10.2021 10:31+1Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.
Обратимся к первоисточнику:
Если мне, на каждый запрос надо генерировать uuid и стандартная библиотека это делает это медленно и я пишу собственную, это лютая остебятена и такой код
...
исключительно лучше понимается проффесионалом, чем такой
Отсюда стороннему наблюдателю (а именно мне) видно, что приведенный вами код демонстрировался именно как тот, который лучше понимается (т.е. к которому вопросов меньше).
Вы его, скорее всего, не увидите, вы увидете что-то типа
Да, я увижу здесь что-то типа C++ и это, знаете ли, как-то вступает в диссонанс с вашим первоначальным комментарием, в котором вы поете хвалебную оду Си и накопленным вами велосипедам.
и у вас вряд ли возникнут вопросы, что тут происходит и есть ли проблемы в реализации
Давайте все-таки обсуждать именно тот пример, который вы привели как "исключительно лучше понимается". Если же вы сами убедились, что пример не показателен, то приведите другой пример, который мог бы проиллюстрировать вашу мысль лучше.
9a75sd
27.10.2021 12:22Любимый момент:
Использует char*-строки и функции <string.h> вместо std::string и std::string_view. (единственное исключение - строковые константы через constexpr).
А это хорошо или плохо? String упрощает взаимодействие со строками, но char* и char str[] дает возможность строго определить занимаемое место. Работаю в основном с программированием микроконтроллеров, и неопределенность, которую вносит string, недопустима, и его стараюсь избегать, используя там, где действительно нужно.
Например, при определении структуры, которая будет передаваться и использоваться в очередях FreeRTOS, string уже не прокатит: недетерминированное поведение строки при передаче через очередь
F0iL Автор
27.10.2021 12:50+1Я специально написал в статье:
Пожалуй, добавлю очевидную оговорку, что для многих описанных практик есть исключения и все зависит от конкретной ситуации.
и пример, когда std::string не подойдет я описал там же в следущем абзаце.
Dovgaluk
27.10.2021 12:24А какая польза от static_cast<int>(number) по сравнению с (int)number?
dvserg
27.10.2021 12:34+2Использование static_cast - это проверка допустимости преобразования во время компиляции.
DistortNeo
27.10.2021 12:42-2Это понятно. Но какая польза от
static_cast<int>(number)
по сравнению с(int)number
в случае, когдаnumber
— это простое выражение с очевидным типом? Очевидный вред отstatic_cast
— это многословность.Одно дело, когда мы работаем с шаблонами, и тогда это всё оправдано. И совсем другое — это что-то вроде:
auto N = (int)ceil(p / 2.0)
Dovgaluk
27.10.2021 12:57+2Так там же написано "number". Если бы "непонятно что" или "указатель", то другое дело.
DistortNeo
27.10.2021 13:02+2Евангелисты C++ утверждают, что надо использовать static_cast всегда и везде, даже в очевидных случаях.
При этом, если почитать посты от PVS Studio с разбором кода проектов, то окажется, что ошибки, связанные с неправильным приведением типов, когда мог бы помочь static_cast, встречаются чуть более, чем никогда.
yeputons
27.10.2021 19:17+5Спор между "так символов меньше" + "программисту очевидно" + "незачем усложнять язык" и "строгая типизация лучше слабой" + "больше проверок компиляции лучше" + "код гораздо больше читается, чем пишется" можно продолжать бесконечно.
Я вот во втором лагере. Данный пример вижу так:
Если вы не используете венгерскую нотацию в том или ином виде, то там будет не
number
, а какое-нибудь другое название.Например, есть ли ошибка в следующем коде, если он компилируется и не падает?
long long timeout_min = ((long long)read_config("timeout_sec") + 59) / 60;
Ответ: а фиг его знает.
Если там возвращается
double
(потому что в JSON только вещественные числа) — то, наверное, тут хотели округлить секунды вверх до минут. А вот если там случайно возвращается сишная строка, то вы только что взяли в качестве количества секунд значение указателя. И никаких предупреждений компилятора не получили даже на самом высоком уровне предупреждений.Разумеется, если протестируете, что таймаут у вас корректно работает, вы сразу обнаружите ошибку. Если протестируете. Или хотя бы выведете на экран.
Но тогда, мне кажется, почём зря приближаемся к другому краю спектра с полным отключением статического анализа типов, как в Python. Да, можно код протестировать, но мне больше нравится получать ошибки компиляции, даже если приходится больше набирать или больше думать.
lorc
28.10.2021 00:55+1с полным отключением статического анализа типов, как в Python
Я сейчас пишу на Пайтоне со включенным статическим анализатором. Боже, как это прекрасно. Я уже забыл что такое когда ты запускаешь программу и она тут же сразу работает.
0xd34df00d
28.10.2021 01:05+1Интересный способ признаться, что статического анализа в питоне недостаточно. Но, в принципе, это неудивительно — всё же система типов mypy и подобных слаба.
lorc
28.10.2021 02:09Совершенно недостаточно.
Я тут пытался написать своего рода "шаблонную" функцию которая принимает тип и потом возвращает значение этого типа. Так и не смог донести до питоновского typing что же я хочу. Т.е. функцию я то написал, но заставить питон выводить правильный тип функции не смог.Но все равно, type annotations - это большой шаг вперед. Я вообще не профессиональный python разработчик, так иногда пишу тулзы разной степени сложности. И даже в мелких скриптах type annotations сильно облегчают жизнь.
yeputons
31.10.2021 16:20Кажется, должны помочь type variables/generics? Например:
from typing import TypeVar T = TypeVar('T') def foo(a: T) -> T: return a x: int = foo(20) # ok y: str = foo('x') # ok z: str = foo(20) # incompatible types in assignment
lorc
01.11.2021 16:56+1Это не совсем то что я хотел:
def get_typed_value(obj: MySpecialObj, T: type) -> Optional[T]: # Perform some checks on "obj".. # .. # And then if isinstance(obj.value, T): return obj.value return None
Так, конечно же вообще не работает. А хотелось бы.
Работает вот такой вариант:
def get_typed_value(obj: MySpecialObj, typename: type) -> Optional[T]:
Но тайпчекер не может вывести правильный тип для вызова
`get_typed_value(o, int)` например.
Вообще я хотел пойти дальше и сделать пачку хелперов вот таким образом:
get_int_value = partial(get_typed_value, typename=int) get_boolean_value = partial(get_typed_value, typename=bool)
и т.д.
Но опять же, тайпчекер не может вывести правильный тип Callable[[MySpecialObj], int] для таких функций (и это понятно, потому что на самом деле partial возвращает не Callable).
Короче, такие приколы возможны в каком-нибудь Хаскеле, но явно не в питоне. А жаль.yeputons
01.11.2021 17:40В чистом Haskell, мне кажется, нельзя, там типы нельзя передавать в качестве аргументов функций. Или получаем зависимые типы (dependent types) и какую-нибудь Agda.
0xd34df00d
01.11.2021 18:29+1Конкретно такое в силу особенностей используемых типов можно только в Idris 2, где есть quantitative type theory и можно паттерн-матчиться на типы.
lorc
01.11.2021 18:56Ну да, я пожалуй неправильно выразился. В Haskell можно использовать какое-то подобие run-time polymorphism, но оно мягко говоря не идиоматично, насколько я понимаю. Поэтому никаких
isinsrance()
. И та проблема, которую я решал (чуть-чуть более продвинутая десериализация YAML) решается совсем по-другому. Похожий кусок кода там просто в принципе не возник бы.
kotlomoy
31.10.2021 13:46+5Чтобы понять, зачем нужен static_cast, достаточно понять, какие проблемы он решает.
В соседнем комментарии привели пример:long long timeout_min = ((long long)read_config("timeout_sec") + 59) / 60;
Код компилируется, но в нем ошибка, если read_config возвращает, например, указатель.
Что делать? Можно исправить код и счастливо пойти дальше собирать те же грабли. А можно что-то придумать, чтобы избежать таких ошибок в дальнейшем.
Что придумать? Например, не разрешать приводить что угодно к чему угодно. Для этого придумали семейство операций: static_cast, dynamic_cast, reinterpret_cast, const_cast. Каждая из них разрешает только некоторые виды приведений типов.
В приведенном примере кода static_cast не разрешит преобразование указателя к целочисленному типу и приведет к ошибке компиляции.
Но вообще говоря, это костыли, борьба с симптомами болезни, а не с самой болезнью. Приведений типов в коде лучше, по возможности, избегать. ЕМНИП поэтому для операций приведения выбрали такие уродливые длинные названия — чтобы мотивировать разработчиков от этих операций избавляться, переписав код (но конечно ни в коем случае не возвращаться обратно к сишному приведению!). Если же по каким-то причинам приведение типа действительно необходимо, то опять хорошо — уродливый синтаксис поможет не забывать об этом опасном месте в коде. Не лишним будет и написать объяснительную в комментарии, что это за место такое, и почему красиво сделать не получилось.
RomanArzumanyan
27.10.2021 12:36+5Почему нигде нет варианта "пишу на Си с классами, потому что в проекте огромная команда, и такой код проще всего развивать и поддерживать"?
Посмотрите в код Open Source проектов - сплошь и рядом Си с классами. Написали на махровых плюсах - половина контрибьюторов минус.
discipuli
27.10.2021 12:59У меня 3 вопроса:
1 Чем #define хуже const при объявлении константы? Насколько я помню он же фактически заменяет одну последовательность символов другой на каком-то из начальных этапов компиляции.
2 А можно поточнее, о каком разделе эмбедеред идёт речь? Точнее на кой нам плюсы даже в 32-битном контроллере для стиралки при том, что на нём можно даже простенькие гуи для сенсорного дисплея писать?
3 И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си? Ядра ж в конце концов на нём пишут. Речь не идёт о ситуации когда вам проще поставить какой нибудь линукс в свой контроллер и писать уже обычные программы.
F0iL Автор
27.10.2021 13:04+2Чем #define хуже const при объявлении константы? Насколько я помню он же фактически заменяет одну последовательность символов другой на каком-то из начальных этапов компиляции.
const в отличие от define обрабатывается не препроцессором, а самим компилятором, поэтому во-первых он уважает scope (если вы объявили его внутри {}, он будет доступен только там и не пролезет наружу, аналогично с неймспейсами и классами), а во-вторых обеспечивает проверку типов на этапе компиляции - меньше вероятности возникновения ошибок из-за неявных преобразований, более понятные сообщения компилятора. А еще есть constexpr, который тоже может сделать жизнь чуточку лучше.
А можно поточнее, о каком разделе эмбедеред идёт речь?
Выше писал уже: разрабатываем коммуникационные шлюзы для ИБП и для зарядных станций автомобилей
... этот шлюз должен уметь в OCPP (JSON поверх Websockets с TLS и авторизацией по сертификатам), MQTT (тоже поверх Websoсkets с TLS), Modbus, BACnet, NTP, всё это с IPv6, а в списке требований к смежным проектам (которые появились еще раньше) на той же платформе есть еще RestAPI с Redfish, Avahi, SNMP, авторизация по LDAP и Radius с ролевой моделью, remote syslog, SSH CLI, кастомные протоколы на базе protobuf, а бонусом еще и сертификация по UL-2900-2-2.
yeputons
27.10.2021 19:04> И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си?
Ни один язык не даёт сакральных преимуществ над другим. Даже если (что не так в случае Си и C++) является строгим надмножеством другого; не всегда больше возможностей — это хорошо.
А конкретная команды в конкретном проекте делает выбор, исходя из ситуации. Язык, тулчейн, стиль кода, архитектура...
Kotofay
29.10.2021 23:43Сакральное преимущество очень простое: Автоматический вызов деструкторов.
yeputons
31.10.2021 16:14Автоматический вызов деструкторов может приводить к нежелательным эффектам. Например, есть следующие стандартные аргументы (мне, впрочем, автоматический вызов всё равно нравится больше альтернатив):
За
}
теперь может скрываться сколь угодно сложная операция, работающее произвольное время и потребляющая произвольное количество ресурсов. Обычно, конечно, небольшое, но может и взорваться. Это же, кстати, стандартный аргумент против перегрузки операторов (с которым тоже можно вести холивары).В частности, если мы пишем какое-нибудь владеющее дерево/список, то при наивной реализации (через
unique_ptr
и сгенерированные деструкторы) для его уничтожения требуется стек размера с глубину этого дерева. Можно случайно переполнить стек и даже не догадаться: рекурсии-то мы нигде явно не писали, она скрыта.Деструкторы провоцируют писать логику для закрытия/сброса буферов именно в деструкторах. Создаётся ложное ощущение, что это безопасно, хотя на самом деле ошибки при закрытии (диск отвалился => не удалось файл записать) тут либо полностью игнорируются, либо приводят к падению приложения. Если же, например, есть явный вызов
close()
, который надо делать абсолютно всегда (либо под угрозой назальных демонов, либо чего-нибудь попроще), то за его наличием следить проще и привычнее.
DistortNeo
31.10.2021 16:37-1Верно, я уже наобжигался с RAII, когда писал многопоточный код. Программа уходит в дедлок. А почему? А потому что логика скрыта в деструкторах, отлаживать которые — дело неблагодарное. С тех пор на апологетов RAII смотрю с большим недовольствием, ресурсы чищу вручную, а деструкторы рассматриваю как обработку аварийных сценариев.
Antervis
31.10.2021 17:31+5думаю что описанная вами проблема является следствием не RAII, а просто обычного недоучтенного юзкейса. То же самое в коде с ручным управлением происходит постоянно. Плюс, корректно освобождать ресурсы, особенно когда могут возникать исключения, очень сложно.
Kotofay
31.10.2021 16:54+2Так разговор шёл именно об "сакральном" отличии С от С++. Почти всё из С++ можно реализовать средствами языка С, но вот это -- к сожалению нет.
И всё, что вы описали, согласитесь, может произойти и при ручном удалении.
Преимущество деструкторов в другом -- на каждый return или "}" не нужно вычислять что ты уже освободил а что ещё нет.
Особенно, если нужно добавить что то в уже существующий код, с множеством управляемых этим кодом ресурсов.
yeputons
27.10.2021 19:24+2Насколько я помню он же фактически заменяет одну последовательность символов другой на каком-то из начальных этапов компиляции.
Ровно этим и хуже (помимо указанного выше). По крайней мере, пока не привыкнуть обходить его острые углы. Вы хотите константу объявить, а не замену символов произвести. Классический пример из олимпиадного программирования:
#define MAXN (int)1e6 + 1 int data[2 * MAXN]; // int data[2 * (int)1e6 + 1];
Хотели объявить массив размера два миллиона и два элемента, а получили массив размера два миллиона и один элемент.
Чинится легко — добавляем вокруг каждой такой макроконстанты круглые скобки.
А дальше выбираем в своём проекте: ничего с этим не делать, забанить
#define
для констант, разрешить и форсить наличие круглых скобок (только где обязательны или вообще везде — отдельный вопрос) на code review/автоматически/не форсить вообще и разгребать редкие последствия.Конечно, если много раз писал
#define
, то скобки наверняка на автомате ставишь. Вот и холивар: кому-то это вообще риском не кажется, просто "надо уметь писать", а кому-то риском кажется. Потом скатываемся в стандартный спор на тему "нужны ли code style", "какой code style использовать"...
hhba
27.10.2021 14:36+3Есть несколько возражений:
1) Код соответствует стандарту С++, если он компилируется компилятором С++. Шах и мат. Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.
2) В общем легко отличить программиста на С++ от программиста С даже без этих пунктов. Если есть код, который можно поглядеть (и доказуемо, что его автор перед вами), то там очевидно все будет. Дальше уже могут быть тонкости, например "этот автор как-то плохо разбирается в поведении string_view".
3) Кажется лучше взять хорошего программиста на С и переучить его на нужный диалект С++, чем упарываться в пуризм.
F0iL Автор
27.10.2021 15:29+2Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.
Что не помешало тому же Бьярне написать C++ Core Guidelines, где он прямым текстом чуть ли не умоляет в современном C++ коде не использовать большинство олдскульных конструкций :)
Кажется лучше взять хорошего программиста на С и переучить его на нужный диалект С++, чем упарываться в пуризм.
А я в статье объяснил, почему нам этот вариант не подходит. Спойлер: в проект уже взяли несколько "хороших программистов на C", и теперь нужно укрепить команду как раз теми, кто будет их переучивать в процессе работы и следить, чтобы они дел не наворотили :)
hhba
28.10.2021 19:09в проект уже взяли несколько "хороших программистов на C", и теперь нужно укрепить команду как раз теми, кто будет их переучивать в процессе работы и следить, чтобы они дел не наворотили :)
Мда, действительно))) Аргумент снимается.
yeputons
27.10.2021 18:56+5Код соответствует стандарту С++, если он компилируется компилятором С++.
Каким из компиляторов? Они, бывает, отличаются и добавляют свои собственные расширения. А ещё поведение отличается между ОС, потому что помимо компилятора есть стандартная библиотека и протекающие абстракции.
Ill-Formed No Diagnostics Required куда делся?
lorc
28.10.2021 00:57+4Код соответствует стандарту С++, если он компилируется компилятором С++
А что, в стандарте С++ убрали undefined behavior?
Kyushu
27.10.2021 17:29Узнал массу интересного про С++. Спасибо, очень полезно. Видимо, меня уже поздно переучивать и я буду и дальше работать в С++, главным образом, Си-шными методами.
mke61
27.10.2021 18:03-4свои пять коп: приплюснутый компайлер считает себя умнее программера, а сишный компайлер считает программера умнее себя. to whom how. ну или ССЗБ.
elektroschwein
27.10.2021 18:22+6приплюснутый компайлер считает себя умнее программера
Вам явно не стоит пользоваться Rust. Там компайлер вообще постоянно считает себя умнее любого программера, и практически всегда оказывается прав. Весь язык на этом построен, можно сказать.
AnthonyMikh
27.10.2021 22:43- std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);
Так можно просто взять
std::pmr::string
из стандартной библиотеки. Или это как раз и подразумевается под "кастомными аллокаторами"?F0iL Автор
27.10.2021 23:36Да, это один из вариантов. Правда, там вроде бы могут быть приколы с fancy pointers, но я с этим не разбирался, это уже вообще отдельная история.
AdamFull
28.10.2021 11:36+2А ещё забыли:
"Использует #ifndef MY_SOMETHING #define MY_SOMETHING #endif вместо #pragma once",
"Использует битовые поля вместо std::bitset."
"Использует сишные библиотеки на прямую без уровня абстракции над ней"
"В заголовочных файлах куча инклудов, которые можно было в принципе там и не писать (incomplete class)"0x3f00
28.10.2021 13:48Увы, имею опыт выстреливания в ногу и с #ifndef HEADER_H..., и с #pragma once.
ldss
29.10.2021 18:12-2Использует ручное управление памятью с new и delete, вместо RAII и умных указателей;
Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки
Для деструкторов забывает virtual :)
А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу
Aldrog
29.10.2021 18:46+3Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки
Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.
А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу
Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.
ldss
29.10.2021 21:16Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.
new/delete - это чисто с++ концепции (вызов конструктора/деструктора при аллокации/очистке памяти), к С не имеют никакого отношения
Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.
[вопрос снимается, ниже ответили]
F0iL Автор
29.10.2021 19:00+2Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки
Да. Но в современном мире C++ уже как лет так 10 ручное управление ресурсами вместо RAII считается дурным тоном (за исключением специфических случаев где это уместно и необходимо). Поэтому использование new и delete часто является одним из признаков того, что человек пришел из языка, где нет умных указателей и подобных оберток и не привык к ним, либо вообще не подозревает о их существовании.
А у вас постоянно встречается ромбовидное наследование, что ли?
А причем здесь ромбовидное наследование вообще? Виртуальный деструктор нужен даже при простом прямом наследовании, когда вы на объект дочернего типа имеете ссылку или указатель типа базового класса, то без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.
ldss
29.10.2021 21:26Поэтому использование new и delete часто является одним из признаков того, что человек пришел из языка, где нет умных указателей и подобных оберток и не привык к ним, либо вообще не подозревает о их существовании.
я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях
Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп
без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.
ясно
ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки
F0iL Автор
30.10.2021 00:39Могу много рассказать про преимущества ручного управления памятью в частных случаях
Никто не спорит, что есть частные случаи, когда такое имеет смысл и оправданно. А вот когда причин особых нет...
я уже давно живу в мире, где есть garbage collectors.
В мире C++ они хоть и не часть языка и стандартной библиотеки, но всё-таки есть. Например, Oilpan GC активно используется в Blink (движок рендеринга браузера Chromium и Android WebView). А ещё есть Boehm.
Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп
Ну да, это незнание возможностей стандартной библиотеки и незнание общепринятых практик написания кода на современном стандарте языка.
К принципам ООП, впрочем, умные указатели отношения имеют мало (разве что в плане самих понятий "конструктор" и "деструктор", на которые они полагаются). Умные указатели можно встретить даже там где вообще нет ООП, например в проектах на чисто процедурном Си, я лично такое видел, как одни наркоманы реализовали что-то подобное с помощью макросов и атрибута cleanup в GCC/Clang. (Upd.: я тут погуглил и даже нашел библиотечку для такого).
ldss
01.11.2021 18:09К принципам ООП, впрочем, умные указатели отношения имеют мало (разве что в плане самих понятий "конструктор" и "деструктор", на которые они полагаются). Умные указатели можно встретить даже там где вообще нет ООП, например в проектах на чисто процедурном Си
тогда посыл статьи не совсем понятен
она тогда всего лишь о том, что новонанятый прогер просто должен отлично знать современную stdНо это ж несерьезно. Умения программиста они не совсем о знании фреймворков
F0iL Автор
01.11.2021 18:39В C это во-первых все-таки относительная редкость и во-вторых там такое невозможно реализовать средствами стандартного языка (только через нестандартные расширения компиляторов), в C++ это во-первых именно часть стандарта еще с лохматых времен, а во-вторых - общепринятая и широкоиспользуемая практика.
Aldrog
30.10.2021 00:44+1я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях
GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.
Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп
Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.
без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.
Здесь на самом деле всё ещё хуже, такой delete вообще является UB, и, например, в моей практике приводил к memory corruption, после чего любая аллокация могла с некоторым шансом вызвать дедлок.
ldss
01.11.2021 18:16GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.
идею-то одна - автоматическое управление памятью
Да и в тех сценариях, где нужно ручное управление, умные указатели будут такими же (если не более) неуклюжими, как и gc
Будем откровенны, смарт поинтеры - это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.
всегда думал, что плюсы это больше про ООП, а не про способы управления ресурцами
F0iL Автор
01.11.2021 18:43это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)
Есть много случаев, когда GC неуместен вообще, например когда у нас real-time или ограничены ресурсы. Просто из-за того, что многие GC явно делают stop-the-world, а если даже и не делают, то накладные расходы на его работу все равно явно ненулевые.
всегда думал, что плюсы это больше про ООП, а не про способы управления ресурцами
Плюсцы это мультипарадигменный язык, и даже при написании кода в чисто процедурном стиле на плюсах вместо голых сей, можно получить немало полезных фишечек и удобств.
ldss
01.11.2021 19:20Есть много случаев, когда GC неуместен вообще, например когда у нас real-time или ограничены ресурсы.
насколько мне известно, в таких случаях будет использоваться скорее с, а не плюсы:))
Просто из-за того, что многие GC явно делают stop-the-world, а если даже и не делают, то накладные расходы на его работу все равно явно ненулевые.
конечно
как и на все фишечки std. Это стандартный трейдоф удобство вс перформанс; но чаще-то всего железо позволяет
помню, один товарищ (игродел) рассказывал, что они даже виртуальные функции стараются не использовать в борьбе за fps - переход по vtable ест пару тактов:) Но это давно еще было
Плюсцы это мультипарадигменный язык, и даже при написании кода в чисто процедурном стиле на плюсах вместо голых сей, можно получить немало полезных фишечек и удобств.
он адски сложный. Сколько там стандарт уже, полторы тыщи страниц? Честно, не вижу особого смысла его ни вспоминать, ни учить.
При этом, взять хотя бы вот это обсуждение - тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык. Такая вещь в себе этот ваш с++
Причем, мне сейчас за этот камент еще и минусов насуют, очень нетерпимая коммьюнити:))F0iL Автор
01.11.2021 19:38+2насколько мне известно, в таких случаях будет использоваться скорее с, а не плюсы:))
Я уже с десяток лет с подобным работаю. Именно на плюсах. И таких проектов все больше и больше.
как и на все фишечки std
Слово "все" здесь совершенно лишнее. В современных плюсах и в std есть немало фишечек с нулевым оверхедом. Ну и сравнивать накладные расходы на вызов виртуальной функции с очередной итерацией работы сборщика мусора - ну такое, там порядки совершенно разные, как и детерминированность временных затрат.
он адски сложный. Честно, не вижу особого смысла его ни вспоминать, ни учить.
Тут каждому своё, вас никто и не заставляет. Я, когда начинал, видел смысл, и до сих пор не жалею.
Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык.
Потому что статья вообще не об этом, с чего бы в ней должно быть про бизнес-логику и алгоритмы? Об этом есть много других статей. Вы бы ещё возмутились, что здесь творчество Пушкина не обсуждают.
При этом, взять хотя бы вот это обсуждение - тут 80% дискуссии о том, как правильно использовать фишки языка.
В каждом языке есть свои хорошие практики и свои понятия чистого и идиоматического кода. Тут нюанс в том, что придти, например, к разработчикам на Java или на C# и начать писать на их языке как на Фортране или на чистом Си не получится, потому что большую часть подобного "олдскула" вам язык сделать так просто не даст, а за другую половину вам шарписты и джависты больно напихают на первом же ревью. А вот на C++ по ряду причин такое сделать получится легко, и как показывает практика, очень многие пытаются так делать. Об этом и статья.
ldss
01.11.2021 20:24Я уже с десяток лет с подобным работаю. Именно на плюсах. И таких проектов все больше и больше.
а на Go или там Rust народ не хочет переходить? Не помню, какой там из них в нативный код компилится
А вот на C++ по ряду причин такое сделать получится легко, и как показывает практика, очень многие пытаются так делать. Об этом и статья.
это понятно, я все еще помню жаркие дискуссии с vs c++ в начале этак 2000х. "Веревка достаточной длины, чтоб выстрелить себе в ногу", а как же:)
Но это, имхо, не достоинство прикладного языкаF0iL Автор
01.11.2021 21:00Не помню, какой там из них в нативный код компилится
Оба в нативный код, но Go с собой рантайм таскает не самый легкий.
а на Go или там Rust народ не хочет переходить?
К Rust присматриваются по-тихоньку.
ldss
01.11.2021 21:36Пора, пора!
Еще kotlin native есть, к слову
У меня был как-то проект, полуembedded (десктоп клиент и управляющая программа на железе), так там на железе натурально комп с winnt стоял, мы туда даже студию вкорячили, для отладки:)) Там с go наверное не было бы проблем, но его тогда и не было еще
Aldrog
01.11.2021 19:54+2При этом, взять хотя бы вот это обсуждение — тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык.
А что ещё вы ожидали от комментариев к статье, поднимающей вопрос идеоматичного кода на C++?
ldss
01.11.2021 20:59так статья, по сути, не об умении программировать, а о знании некоторых специфических (пусть и широко распространенных) особенностей фреймворка (std)
Мне такое странно, фреймворк и фреймворк, нормальный прогер основные положения выучит за пару месяцев, это не рокет саенс. А тут прям такое внимание
Aldrog
01.11.2021 21:46+2Статья о сравнении концепций, которые принято применять в C и C++. Да, часть из них реализуется с помощью абстракций, которые не входят в сам язык, но 1) от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL 2) другие фреймворки в большинстве своём всё равно либо опираются на STL, либо предлагают аналогичные абстракции — использовать сишные строки или вручную управлять памятью вы наверняка не будете, независимо от фреймворка.
ldss
02.11.2021 17:47от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL
по статье это не заметно:)
0xd34df00d
02.11.2021 22:31+2простой советский…
bool foo(int n) { int arr[] = { 0, 1, 2, 3, 4 }; return std::any_of(std::begin(arr), std::end(arr), [n](int v) { return v < n; }); }
компилируется gcc 11 в
_Z3fooi: test edi, edi setg al ret
тогда как
bool foo(int n) { int arr[] = { 3, 2, 0, 1, 4 }; return std::any_of(std::begin(arr), std::end(arr), [n](int v) { return v < n; }); }
компилируется в какую-то наркоманию
_Z3fooi: cmp edi, 2 jg .L3 test edi, edi setg al ret .L3: mov eax, 1 ret
Antervis
01.11.2021 19:17+2идею-то одна - автоматическое управление памятью
в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже
Да и в тех сценариях, где нужно ручное управление, умные указатели будут такими же (если не более) неуклюжими, как и gc
Будем откровенны, смарт поинтеры - это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)
давайте для начала вспомним что в сравнении с GC умные указатели не вносят оверхеда, то есть уже применимы в большем числе юзкейсов. Во-вторых, сам по себе GC не дает абсолютно ничего в сравнении с умными указателями, кроме некоторого снижения когнитивной нагрузки, т.е. недостаточная выразительность языка компенсируется за счет доп. расходов рантайма.
А ручное управление памятью в языке с GC это костыль для бекдора в другом костыле...
всегда думал, что плюсы это больше про ООП, а не про способы управления ресурцами
а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект. RAII позволяет делать герметичные объекты, которые при уничтожении освобождают все ресурсы. Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.
ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки
учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...
ldss
01.11.2021 19:45в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже
это без разницы, обьекты расположены в памяти. Ну, разве что вы имеете в виду некие ресурсы на диске, обьекты ядра и проч. Но суть все равно та же - автоматически освобождать ресурсы, когда они не нужны
Во-вторых, сам по себе GC не дает абсолютно ничего в сравнении с умными указателями, кроме некоторого снижения когнитивной нагрузки
он дает чудовищное снижение этой самой нагрузки
Попишите, например, год на сишарпе или там жаве, и потом опять на плюсах - разницу сразу прочувствуете.
т.е. недостаточная выразительность языка компенсируется за счет доп. расходов рантайма.
это не к языку, это к CLR/virtual machine
Что касается рантайма - разница там не сказать чтоб значительная, к тому же выделение памяти в managed heap сильно быстрее, чем в нативной, в силу отсутствия дефрагментации; да и современные gc нонеча работают уже совсем не так как давеча
а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект
одно из самых странных обьяснений ООП что я слышал, если честно
противоречит хотя бы тому же single responsibility
Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.
не, не происходит:) Ну разве что с кастомными плохо спроектированными обьектами, использующими unmanaged resources
учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...
каким образом понимание программирования на с++ связано с пониманием программирования вообще? Имхо, тут скорее наоборот, знание с++ требует столько умственных усилий, что на собственно программирование их и не остается
F0iL Автор
01.11.2021 19:50Но суть все равно та же - автоматически освобождать ресурсы, когда они не нужны
В том же Шарпе это будет не "когда они не нужны", а "когда они не нужны и до них наконец-то доберется сборщик мусора". Поэтому в языке пришлось изобретать костыли с Dispose() и using.
ldss
01.11.2021 20:17В том же Шарпе это будет не "когда они не нужны", а "когда они не нужны и до них наконец-то доберется сборщик мусора". Поэтому в языке пришлось изобретать костыли с using.
ну что вы, давным давно уже не так
запустится он, когда у него перестанет хватать памяти под новые обьекты, при этом, все обьекты он проверять на удаление не будет. А пока ее хватает, какие проблемы
Была тут вроде переводная статья про gc как эмулятор бесконечной памяти
что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейс
такой аналог try/finallyF0iL Автор
01.11.2021 21:11при этом, все обьекты он проверять на удаление не будет.
ну да, там поколения еще есть, если я правильно помню.
что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейсЯ в курсе. Это я к тому, что в плюсах с этим проще, там RAII с умными указателями подходят и используются для обоих случаев - и для освобождения ресурсов, и для автоматического вызова закрывающей функции :)
ldss
01.11.2021 21:58ну да, там поколения еще есть, если я правильно помню.
там много чего есть:)
Это я к тому, что в плюсах с этим проще, там RAII с умными указателями подходят и используются для обоих случаев - и для освобождения ресурсов, и для автоматического вызова закрывающей функции
а если мне закрыть надо, а ресурс освобождать нет?:) Они ж довольно примитивные, unique_ptr эти. Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?
Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?
Aldrog
02.11.2021 11:01+1а если мне закрыть надо, а ресурс освобождать нет?
То вероятно это должны быть два разных уровня абстракции.
Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?
unique_ptr
называется unique не просто так, дваunique_ptr
не могут ссылаться на один объект. Для таких случаев существуетshared_ptr
, который ведёт счётчик ссылок и удаляет объект только вместе с последней ссылкой.Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?
Нет, обычно полагаются на вызов деструктора при выходе из скоупа. Вызов
unique_ptr::reset()
практически полностью аналогиченdelete
и существует для тех ситуаций, когда вам всё-таки необходимо ручное управление ресурсами.
ldss
02.11.2021 18:19unique_ptr называется unique не просто так, два unique_ptr не могут ссылаться на один объект.
в документации ничего про это не сказано. Это отдано на откуп программисту, что ли? Ну так это и есть та самая веревка, которой можно выстрелить себе в ногу
Нет, обычно полагаются на вызов деструктора при выходе из скоупа
Понятно
Тогда мы снова возвращаемся к ситуации, когда мне (в случае необходимости немедленного освобождения памяти) нужно писать ручное удаление, которое еще и черти к чему приведет
О чем я и говорю, очень примитивная обертка, не гарантирующая, по сути, ни от чего
F0iL Автор
02.11.2021 19:03+2в документации ничего про это не сказано
Прямым текстом сказано вообще-то:
Более того, если вы пользуетесь только умными указателями и не достаете из них / не пихаете в них сырые, то у вас в принципе не может возникнуть ситуация, когда на один объект указывают сразу несколько unique_ptr'ов.
Тогда мы снова возвращаемся к ситуации, когда мне (в случае необходимости немедленного освобождения памяти) нужно писать ручное удаление, которое еще и черти к чему приведет
Зачем? Оно гарантированно автоматически немедленно удалится когда вы выйдете из скоупа, причем вне зависимости от того, как именно выйдете. Объявили объект внутри функции - он удалиться как только произойдет выход из функции. Объявили объект внутри блока {} - он удалится когда закончится этот блок. Объявили объект внутри другого объекта - он удалится когда удалится родительский объект.
В конце концов, если вам непонятно зачем, но хочется сделать это еще раньше, никто не запрещает вручную сделать release(), и "к черти чему" это не приведет - точно так же вызовется деструктор объекта, как и во всех других случаях, никаких неожиданностей.
ldss
04.11.2021 18:10Прямым текстом сказано вообще-то:
дык а как это обеспечивается на уровне реализации этого UP? что мне мешает сделать указатель на обьект при помощи new и запихнуть его в 2 разных UP? Тока не говорите, что здравый смысл - это а) как повезет б) в сложных codebases вручную все проконтролировать невозможно
Объявили объект внутри блока {} - он удалится когда закончится этот блок
я, может, неправильно все делаю, но что-то вот в таком раскладе деструктор у меня не вызывается
class AutoCheck { public: AutoCheck(int i) { inner = i; } ~AutoCheck() { inner = 0; } private: int inner; }; int main() { auto test = new AutoCheck(10); { auto p = std::make_unique<AutoCheck*>(test); auto p1 = std::make_unique<AutoCheck*>(test); } }
F0iL Автор
04.11.2021 19:48в сложных codebases вручную все проконтролировать невозможно
Элементарно. Просто не использовать new вообще. Юники гораздо лучше создаются через make_unique(), шаредные через make_shared(): во-первых "контроллировать" ничего не придется, во-вторых при использовании, например, в аргументах метода, нет риска утечки при выкидывании исключения между созданием и присваиванием, в-третьих в случае с make_shared это даже выходит более производительнее (одна аллокация вместо двух).
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rr-make_shared
я, может, неправильно все делаю, но что-то вот в таком раскладе деструктор у меня не вызывается
У вас в принципе семантически некорректный код.
А при выходе из скоупа все работает: https://onlinegdb.com/hwvjZ64DM
ldss
04.11.2021 18:46похоже, мы всю дорогу о разном говорили.
unique_ptr тут вообще сбоку, говорить надо было про auto_ptr
F0iL Автор
04.11.2021 19:34+1auto_ptr уже 10 лет как deprecated и уже 4 года как удален из стандарта языка.
ldss
05.11.2021 20:13угу, вижу
Суть моих филиппик очень простая - сам по себе язык и окружение не гарантируют ни от чего; как и 20 лет назад, надо самому за всем внимательно следить.
Aldrog
02.11.2021 19:03+2в документации ничего про это не сказано. Это отдано на откуп программисту, что ли? Ну так это и есть та самая веревка, которой можно выстрелить себе в ногу
У него просто нет копирующего конструктора / присваивания, только перемещающие.
Выстрелить себе в ногу можно разве что хитро извернувшись в стилеunique_ptr{other_ptr.get()}
.Тогда мы снова возвращаемся к ситуации, когда мне (в случае необходимости немедленного освобождения памяти) нужно писать ручное удаление, которое еще и черти к чему приведет
Скоуп — любой блок кода в фигурных скобках, если ресурс должен быть занят только при выполнении нескольких строчек кода, которые не хочется выносить в функцию, то эти строки просто оборачиваются в фигурные скобки.
Обычная практика в плюсах для блокировки мутекса, например.
ldss
04.11.2021 18:13У него просто нет копирующего конструктора
auto test = new int(10); auto p = std::make_unique<int*>(test); auto p1 = std::make_unique<int*>(test);
а так?
Antervis
04.11.2021 18:55+1Тока не говорите, что здравый смысл - это а) как повезет б) в сложных codebases вручную все проконтролировать невозможно
привести искуственный пример самострела можно в любом языке. И точно так же невозможно вручную проконтроллировать отсутствие его в достаточно большой кодобазе. Ну и не просто так рекомендуется использовать make_unique.
Что до здравого смысла - он ведь не дает вам например выпрыгнуть из окна, верно?
а так?
вы видите тут вызов копирующего конструктора?
ldss
05.11.2021 20:00привести искуственный пример самострела можно в любом языке. И точно так же невозможно вручную проконтроллировать отсутствие его в достаточно большой кодобазе. Ну и не просто так рекомендуется использовать make_unique.
я о том, что в силу особенностей языка использование unique_ptr на спасет ни от чего, выстрелить в ногу можно точно так же, как и 20 лет назад
просто тогда б-м прошаренный народ писал свои обертки и использовал всякие creator patterns
а вот наличие gc от такого вполне так себе гарантируeт
F0iL Автор
05.11.2021 21:01в силу особенностей языка использование unique_ptr на спасет ни от чего
Использование unique_ptr защищает об большинства ошибок по невнимательности, но не защитит, если вы упорно намеренно и осознанно пытаетесь выстрелить себе в голову.
ldss
05.11.2021 21:10Использование unique_ptr защищает об большинства ошибок по невнимательности, но не защитит, если вы упорно намеренно и осознанно пытаетесь выстрелить себе в голову.
или использую его неправильно, от чего язык не защищает никак
Я, собственно, об этом в основном. Т.е., язык практически не изменился, просто оброс некими обертками, кои можно использовать правильно, можно неправильно, а можно и вообще не использовать.
Это мне напоминает одну контору в конце 90х, в которой было запрещено использовать арифметические операции с указателями - чтоб, зчачить, не попортить чего
DistortNeo
04.11.2021 18:56+1А что вы этим кодом хотели показать? Что у типа
int *
есть конструктор копирования? Ну так не используйте указатели, и будет вам счастье.Нормальный человек же напишет:
auto test = std::make_unique<int>(10);
После чего закономерно словит ошибку компиляции.
DistortNeo
05.11.2021 20:16Конечно же словит. Нельзя привести
std::unique_ptr<int>
кint*
.Можно написать так, выстрелив в ногу через
.get()
:auto test = std::make_unique<int>(10); auto p = std::make_unique<int*>(test.get()); auto p1 = std::make_unique<int*>(test.get());
А можно написать так:
auto test = std::make_unique<int>(10); auto p = std::make_unique<std::unique_ptr<int>>(std::move(test)); auto p1 = std::make_unique<std::unique_ptr<int>>(std::move(test));
Никаких проблем, кстати, со вторым кодом не будет. Но это просто дичь.
ldss
05.11.2021 21:05Конечно же словит. Нельзя привести std::unique_ptr к int*.
я просто не понял, что вы имели в виду
Aldrog
04.11.2021 20:48+2Стоило бы сначала прочитать документацию
make_unique
иunique_ptr
, ваш код эквивалентенint* test = new int(10); int** p = new int*(test); int** p1 = new int*(test); delete p1; delete p;
Скорее всего вы имели в виду
auto test = new int(10); std::unique_ptr<int> p(test); std::unique_ptr<int> p1(test);
А я могу привести ещё один пример:
struct RecVector : std::vector<RecVector> { using std::vector<RecVector>::vector; };
И сделав
RecVector a; a.push_back(a);
Вы получите утечку памяти.
Знаете, чем этот пример отличается от вашего?
Тем, что я готов поверить в то, что что-то подобное, хоть и маловероятно, но может случайно возникнуть в большом и сложном проекте.А само наличие
new
совершенно очевидно указывает на проблемное место, и ни один плюсовик, знакомый с хорошими практиками, такую ошибку никогда не допустит, и через кодревью не пропустит.В C++ есть множество способов выстрелить себе в ногу, но не надо приплетать к ним случаи, возможные только в искуственных примерах.
ldss
05.11.2021 20:06А само наличие new совершенно очевидно указывает на проблемное место, и ни один плюсовик, знакомый с хорошими практиками, такую ошибку никогда не допустит, и через кодревью не пропустит.
ну т.е. в языке есть задокументированная в стандарте конструкция, пользоваться которой нельзя:) Понятно:)
Скорее всего вы имели в виду
да я уже посмотрел внутрь make_unique, вижу, что он указатель на переданное value создает
А как быть с созданием картинок, например, в цикле? Создал-обработал-автоудалил? Мне штож, в стеке их создавать?В C++ есть множество способов выстрелить себе в ногу, но не надо приплетать к ним случаи, возможные только в искуственных примерах.
как-то, говорю ж, с 98 года не сильно все изменилось
F0iL Автор
05.11.2021 21:10+1в языке есть задокументированная в стандарте конструкция, пользоваться которой нельзя
Можно, если вам это действительно нужно и вы понимаете, что и зачем вы делаете.
C++ - как язык, во-первых нацелен на максимальную гибкость при необходимости, во-вторых на интероперабельность с C и старым кодом (из-за этого много проблем, да, но кучу легаси, увы, не выкинуть), в третьих на производительность (знаменитый принцип you don't pay for what you don't use).
Поэтому в стандарте языка есть много чего на разные случаи жизни, одними вещами разработчики пользуются когда просто пишут код и хотят поменьше заморачиваться, а другими пользуются в специфических случаях, типа достижения максимального перформанса, выполнения всяких необычных трюков, интеграции с древним 3rd-party кодом, и т.д. Язык дает возможность выбора, за это его, собственно, многие и ценят. Естественнно, при этом требуются определенные знания, в каких случаях использовать то и то и быть внимательным насчет вот такого, а в каких случаях то использовать не надо, а лучше использовать это и вот это. И поэтому никто и не рекламирует плюсы как язык для домохозяек и младшей школы :)А как быть с созданием картинок, например, в цикле?
А в чем сложность? Сделали make_unique внутри цикла, он автоматически удалит созданную картинку при завершении итерации.
как-то, говорю ж, с 98 года не сильно все изменилось
Изменилось все настолько сильно, что я бы сказал, что C++98 и C++17 - это вообще почти что два разных языка. И второй гораздо лучше.
ldss
05.11.2021 21:33Можно, если вам это действительно нужно и вы понимаете, что и зачем вы делаете.
если судить по исходной статье и обсуждению, такого знающего просто на работу не возьмут:))
А в чем сложность? Сделали make_unique внутри цикла, он автоматически удалит созданную картинку при завершении итерации.
вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?
Изменилось все настолько сильно, что я бы сказал, что C++98 и C++17 - это вообще почти что два разных языка
эт не язык изменился, это стандартная библиотека изменилась
Aldrog
06.11.2021 00:14+2вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?
Может, наконец, прочитаете документацию?
make_unique переадресует аргументы в конструктор типа, его суть ровно в том, что вам не нужно сначала создавать объект, а потом помещать в unique_ptr, как приходилось делать до c++14.эт не язык изменился, это стандартная библиотека изменилась
Обсуждаемые нововведения были бы малополезны без появившегося в c++11 move semantics.
F0iL Автор
06.11.2021 00:31+1если судить по исходной статье и обсуждению, такого знающего просто на работу не возьмут:))
В исходной статье вообще-то прямым текстом написано:
для многих описанных практик есть исключения и все зависит от конкретной ситуации ... если человек может грамотно обосновать использование или неиспользование той или иной языковой конструкции или апишки - то это уже говорит о его понимании и должно зачитываться только в плюс.
вопрос, что мне передавать в пареметр make_unique?
Ровно то же самое, что вы передавали бы в конструктор при создании картинки на стеке или через new.
эт не язык изменился, это стандартная библиотека изменилась
Во-первых стандартная библиотека это неотъемлемая часть языка, во-вторых нет, со времён C++98 добавилось именно что много ценных изменений в сам язык, а не только в стандартную библиотеку (та же move-семантика и rvalues, constexpr и constexpr if, гарантии потокобезопасности при инициализации static'ов, range based for и structured bindings, лямбды, гарантированный copy epsilon, и т.д.)
Antervis
01.11.2021 21:19+1это без разницы, обьекты расположены в памяти. Ну, разве что вы имеете в виду некие ресурсы на диске, обьекты ядра и проч. Но суть все равно та же - автоматически освобождать ресурсы, когда они не нужны
файловые дескрипторы, сокеты, мьютексы в конце концов. RAII дает куда больше чем простое освобождение памяти.
он дает чудовищное снижение этой самой нагрузки
Попишите, например, год на сишарпе или там жаве, и потом опять на плюсах - разницу сразу прочувствуете.
вы сильно недооцениваете моё понимание плюсов и сильно переоцениваете моё понимание джавы/шарпа.
это не к языку, это к CLR/virtual machine
по факту они скорее нераздельны
Что касается рантайма - разница там не сказать чтоб значительная, к тому же выделение памяти в managed heap сильно быстрее, чем в нативной, в силу отсутствия дефрагментации; да и современные gc нонеча работают уже совсем не так как давеча
Единственный вариант когда managed heap теоретически может быть быстрее - если специфика нагрузки хорошо ложится на конкретный аллокатор. Опять же, такие сценарии в плюсах покрываются переопределением аллокатора. В противном случае GC всегда будет медленнее, а идеальный GC по сути сводится к автоматическому управлению. Подробнее можете ознакомиться в этом докладе
одно из самых странных обьяснений ООП что я слышал, если честно
противоречит хотя бы тому же single responsibility
Во-первых, вы смешиваете категории. Парадигма ООП основана на инкапсуляции, а принцип единственной ответственности - всего лишь паттерн ООП. Во-вторых, здесь нет никакого противоречия. Простой пример: класс File обычно будет инкапсулировать файловый дескриптор (ресурс), данные (путь до файла, состояние, режим открытия), ну и соответственно логику работы с ним (методы открыть/закрыть/читать/писать). При этом SRP не нарушается, ведь File всё еще выполняет ровно одну задачу.
каким образом понимание программирования на с++ связано с пониманием программирования вообще? Имхо, тут скорее наоборот, знание с++ требует столько умственных усилий, что на собственно программирование их и не остается
я критикую ваше понимание ООП а не с++. А еще вы опять смешиваете категории, на этот раз "я не справился с с++" и "с++ не нужен"
запустится он, когда у него перестанет хватать памяти под новые обьекты, при этом, все обьекты он проверять на удаление не будет. А пока ее хватает, какие проблемы
действительно, какие проблемы выжрать 300 гигов памяти на серваке, а потом потратить десяток секунд на её очистку, пока все остальные приложения ждут...
что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейссобственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов?
ldss
01.11.2021 22:22файловые дескрипторы, сокеты, мьютексы в конце концов. RAII дает куда больше чем простое освобождение памяти.
да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец
вы сильно недооцениваете моё понимание плюсов и сильно переоцениваете моё понимание джавы/шарпа.
ну, вот у меня был такой опыт. Очень освежающий!! Когда через пару дней активной разработки мы вдруг сообразили - мама дорогая, это ж не дотнет, надо ж самим обьекты прибирать! И началось..
Единственный вариант когда managed heap теоретически может быть быстрее - если специфика нагрузки хорошо ложится на конкретный аллокатор.
? Managed heap - это непрерывный кусок памяти, уже заранее выделенный вирт. машиной, какие там спец аллокаторы. Для небольших обьектов никакой там магии нет
Подробнее можете ознакомиться в этом докладе
в докладе говорится, что гц добавляет оверхед - с этим-то я вообще не спорил; я говорил, что а) на современном железе на него чаще всего пофиг, б) помещение обьекта в управляемую память - быстрее
ну и докладчик не очень знает про современные стратегии сборки мусора:)
Парадигма ООП основана на инкапсуляции
нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО
действительно, какие проблемы выжрать 300 гигов памяти на серваке, а потом потратить десяток секунд на её очистку, пока все остальные приложения ждут...
ну так если вы настроили вирт. машину так, что ей можно 300 гиг памяти, кто ж виноват
Кроме того, железо нынче дешевое, а программисты дорогие, так что, опять же, это не такая уж проблема.
Иными словами, пофиг, кто сколько сожрал памяти, если ее хватает
собственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов
нет, конечно, потому что я гарантированно закрываю открытые файлы
IDisposable - это просто интерфейс, позволяющий имплементировавшему его классу быть использованным в конструкции using. Как имплементацию напишете, так и будет - ровно как и в случае с деструктором вашего файлового обьекта
Antervis
01.11.2021 23:31+2да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец
это и называется "протекающие абстракции"
? Managed heap - это непрерывный кусок памяти, уже заранее выделенный вирт. машиной, какие там спец аллокаторы. Для небольших обьектов никакой там магии нет
это в теории там просто непрерывный последовательно заполняющийся кусок памяти. На практике это конечно же не работает (фрагментация уводит потребление памяти в космос) и приходится реализовывать полноценные аллокаторы, распределяющие память внутри этой managed heap.
б) помещение обьекта в управляемую память - быстрее
по сути это получается полноценная деаллокация. Грубо говоря, любой аллокатор, который используется внутри managed heap, можно просто подключить к плюсам и, если это выгодно, получить профит, безо всяких затрат на GC. Просто обычно это не делают потому, что это не выгодно.
ну и докладчик не очень знает про современные стратегии сборки мусора:)
а это не важно. Сколько ни оптимизируй/усложняй стратегию сборки мусора, отрицательной или даже нулевой её стоимость не станет.
нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО
ООП по версии Алана Кая конечно больше похож на то, что нынче называют "акторами", но инкапсуляция в его парадигме всё так же фундаментально необходима
на современном железе на него чаще всего пофиг ... железо нынче дешевое, а программисты дорогие ... пофиг, кто сколько сожрал памяти, если ее хватает
Во-первых, вы предполагаете что разработка на языках с GC быстрее чем на тех же плюсах. Начиная с с++11 это как минимум не очевидно и не безусловно. Во-вторых, не всегда можно пренебречь стоимостью железа и не всегда есть его стократный запас. При сравнимой стоимости разработки ценник в $1m за облачную инфраструктуру покажется куда привлекательнее $5m.
ldss
02.11.2021 18:10-1это в теории там просто непрерывный последовательно заполняющийся кусок памяти. На практике это конечно же не работает (фрагментация уводит потребление памяти в космос) и приходится реализовывать полноценные аллокаторы, распределяющие память внутри этой managed heap.
да какая теория? При каждом коллекте gc дефрагментирует managed heap. Это гарантированно всегда непрерывный кусок памяти
Вот чесслово, почитайте доку уже
https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentalsа это не важно. Сколько ни оптимизируй/усложняй стратегию сборки мусора, отрицательной или даже нулевой её стоимость не станет.
не станет, но дьявол кроется в деталях
Во-первых, вы предполагаете что разработка на языках с GC быстрее чем на тех же плюсах. Начиная с с++11 это как минимум не очевидно и не безусловно
вполне очевидно; c++ заметно более сложный и дорогой в обслуживании язык. Начиная с разницы между обьектами, ссылками и указателями и заканчивая упоминавшимися вирт. деструкторами. Да и прочее, те же ексепшены взять
При сравнимой стоимости разработки ценник в $1m за облачную инфраструктуру покажется куда привлекательнее $5m.
вряд ли будет такая разница, цифры-то с потолка. При этом, найти толкового дева на плюсах сложнее и затратнее, чем, скажем, на дотнете или жаве - будь это иначе, не было б такой разницы в количестве тех и других
ООП по версии Алана Кая конечно больше похож на то, что нынче называют "акторами", но инкапсуляция в его парадигме всё так же фундаментально необходима
да все равно unique_ptr под это дело не очень попадает
инкапсуляция - сокрытие данных, которые определяют состояние обьекта, а указатель внутри up него не определяет, это просто обертка
Antervis
02.11.2021 20:18+2да какая теория? При каждом коллекте gc дефрагментирует managed heap. Это гарантированно всегда непрерывный кусок памяти
ну по сути накладные расходы на аллокацию заменены куда большими во время сборки мусора, которая еще и блочит все треды.
не станет, но дьявол кроется в деталях
какой блин "дьявол" то? У вас есть дополнительные расходы на механизм, который своим существованием не может сэкономить ресурсы даже теоретически.
вполне очевидно; c++ заметно более сложный и дорогой в обслуживании язык. Начиная с разницы между обьектами, ссылками и указателями и заканчивая упоминавшимися вирт. деструкторами. Да и прочее, те же ексепшены взять
то, что язык сложнее, не значит, что писать на нём тоже сложнее. Взять например питон - на дистанции в 1-10 kloc писать на нём проще чем на чем угодно другом; писать на питоне проект на 100+ kloc - самоубийство.
вряд ли будет такая разница, цифры-то с потолка
ну оверхед джавы/шарпа относительно плюсов по CPU порядка 2-4 раз плюс стоимость stop the world, по памяти больше. Моя оценка не то чтобы консервативна, но в реальных пределах. По порядкам цифры тоже вполне реальные.
При этом, найти толкового дева на плюсах сложнее и затратнее, чем, скажем, на дотнете или жаве
вы посмотрите средние ЗП плюсовиков и джавистов, удивитесь
да все равно unique_ptr под это дело не очень попадает
инкапсуляция - сокрытие данных, которые определяют состояние обьектаего задача - быть тонкой владеющей оберткой для объекта, размещенного в куче. unique_ptr инкапсулирует сырой указатель T* _ptr, не давая его менять кроме как через методы unique_ptr. По мне так тут всё совершенно логично и корректно. Нелогично было бы если бы приходилось для каждого объекта unique_ptr<T> писать финализатор, который вызовет ~T(), а память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения
DistortNeo
02.11.2021 21:28ну оверхед джавы/шарпа относительно плюсов по CPU порядка 2-4 раз
Я недавно делал замеры скорости работы алгоритмов обработки изображений на C# и C++: C++ оказывался в среднем всего на 20% быстрее, чем C#. Разница в разы была, наверное, лет 10 назад.
Kotofay
03.11.2021 17:22Недавно столкнулся стем что на Java LU-разложение быстрее чем на С++, на несколько процентов. Исходные коды идентичны.
Правда использовался GraalVM vs Visual C++.
F0iL Автор
03.11.2021 17:49Такое вполне может быть, если жавовский JIT хорошо оптимизировал "горячий код" под конкретный процессор, а бинарник на С++ был собран со стандартными параметрами компилятора (да и майкрософтовский компилятор, насколько я помню, стараниями в оптимизации не сильно блещет и еще год назад на многих кейсах сливал gcc и clang'у).
Kotofay
03.11.2021 18:14В том то и дело что использовался CLang и максимально возможные оптимизации, + AVX.
Честно говоря я был немного в шоке от GraalVM.
ldss
04.11.2021 18:38ну по сути накладные расходы на аллокацию заменены куда большими во время сборки мусора, которая еще и блочит все треды.
ну так в реалиях-то как раз эта сборка происходит редко, да и сбор в первой генерации быстрый. При этом не будет out of memory из-за дефрагментации памяти
какой блин "дьявол" то? У вас есть дополнительные расходы на механизм, который своим существованием не может сэкономить ресурсы даже теоретически.
теоретически - да
А на практике - очень даже. Потому что сбор мусора происходит редкото, что язык сложнее, не значит, что писать на нём тоже сложнее.
именно это оно и значит:))
Взять например питон - на дистанции в 1-10 kloc писать на нём проще чем на чем угодно другом; писать на питоне проект на 100+ kloc - самоубийство
ну так давайте не питон возьмем, а сишарп, или там, свят-свят, жаву
вы посмотрите средние ЗП плюсовиков и джавистов, удивитесь
да зачем мне смотреть, мы весь прошлый год искали
сишарп/жава - без проблем, а сишников тока в экс-ссср и нашли, дядьку уже за 40Нелогично было бы если бы приходилось для каждого объекта unique_ptr писать финализатор, который вызовет ~T(),
ну в с++ наверное
а в сишарпе это просто деструктора память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения
финализация идет в отдельном потоке, о чем вы?
И еще раз замечу - вы чересчур переоцениваете необходимость своевременной чистки памяти. В частных случаях - да, в целом нет
Antervis
05.11.2021 20:57+1теоретически - да
А на практике - очень даже. Потому что сбор мусора происходит редкочем реже запускается сбор мусора, тем больше ресурсов надо почистить, тем больше времени уходит на чистку. По сути это просто некоторые N% времени потраченные на GC. И этот N >> 0
именно это оно и значит:))
попробуйте написать что-то сколь угодно сложное на таком примитивно-тривиальном ЯП как баш и вы поймете о чем я
ну так давайте не питон возьмем, а сишарп, или там, свят-свят, жаву
ну так они на порядок сложнее питона, что в очередной раз подтверждает мой аргумент
да зачем мне смотреть, мы весь прошлый год искали
сишарп/жава - без проблем, а сишников тока в экс-ссср и нашли, дядьку уже за 40может быть потому что вы искали "сишников" а не "плюсовиков"? Или потому что вы искали джуна, а спрашивали как с сеньора? В общем, сомнительный результат сомнительного эксперимента. В то время как рынок куда более объективен.
а в сишарпе это просто деструктор
который вызывается когда конкретно?
финализация идет в отдельном потоке, о чем вы?
синхронизированным с остальными, что ведет к их замедлению. Плюс, количество "отдельных потоков" и ресурсов CPU на их исполнение в общем случае не бесконечно.
И еще раз замечу - вы чересчур переоцениваете необходимость своевременной чистки памяти. В частных случаях - да, в целом нет
вот беру я рабочий ноут с CLion, вижу потребление RAM в 2.5 Гб, тыкаю "очистить", вижу 850Мб. Система репортит потребление CLion'а в 4.5 Гб + 1.3 Гб clang code model. Естественно настроен ShenandoahGC + ExperimentalVMOptions + GuaranteedGCInterval, т.к. без этого потребление памяти CLion'ом достигало 13 гигов а при инициации чистки мусора можно было смело идти пить чай. И ладно бы это было раз в день, а не каждые 1-2 часа. Вся эта память могла бы использоваться на что-то полезное, например на браузер. Вот это частный случай, или всё-таки общий?
0xd34df00d
02.11.2021 22:47+1Иными словами, пофиг, кто сколько сожрал памяти, если ее хватает
На самом деле проблема с GC в том, что чем меньше дополнительной памяти ему доступно, тем медленнее он работает.
У меня в практике есть проекты, которые требовали сотню-другую гигабайт памяти (матрицы с недобигдатой вертали по-всякому), и запускались на машинах с 384-512 гигабайтами ОЗУ. GC туда бы уже не влезло.
0xd34df00d
02.11.2021 22:44+1RAII дает куда больше чем простое освобождение памяти.
RAII не даёт ничего, кроме, по факту, «выполни вот этот код при выходе из скоупа». В частности, нет никаких гарантий, что на ресурс, который подчистит ваш RAII-класс, не останется никаких ссылок (вроде сырого указателя или ссылки на объект под умным указателем,
native_handle
уstd::thread
и прочих подобных, етц).Такие гарантии (вместе с предсказуемым потреблением ресурсов) дают только типы.
Простой пример: класс File обычно будет инкапсулировать файловый дескриптор (ресурс), данные (путь до файла, состояние, режим открытия), ну и соответственно логику работы с ним (методы открыть/закрыть/читать/писать). При этом SRP не нарушается, ведь File всё еще выполняет ровно одну задачу.
А потом вы захотите управлять не только файлами на диске, но и чем-нибудь mmap'ленным, например, и понятие дескриптора файла надо будет как-то выносить отдельно.
Antervis
02.11.2021 23:09+1RAII не даёт ничего, кроме, по факту, «выполни вот этот код при выходе из скоупа»
Во-первых, этого более чем достаточно противопоставляя "когда-нибудь GC подчистит память, остальное будь добр руками". Во-вторых, не обязательно "при выходе из скоупа". В третьих, вы опять придумываете гипотетические проблемы.
Такие гарантии (вместе с предсказуемым потреблением ресурсов) дают только типы.
давайте для начала вспомним что в чисто функциональном ЯП степень свободы изначально намного ниже и вводить/выдерживать контракты сильно проще. По факту если в плюсах писать в чисто функциональном стиле, пользуясь только иммутабельными объектами, вы тоже можете получить весьма надежный код.
А потом вы захотите управлять не только файлами на диске, но и чем-нибудь mmap'ленным, например, и понятие дескриптора файла надо будет как-то выносить отдельно.
не вижу проблемы. Можно расширить функционал имеющейся абстракции, можно ввести новую, можно ввести новую и потом обернуть вместе с имеющейся. В общем, заниматься разработкой. Не понимаю где что и чему тут мешает.
0xd34df00d
02.11.2021 23:23+1Во-первых, этого более чем достаточно противопоставляя "когда-нибудь GC подчистит память, остальное будь добр руками".
Мы обсуждаем производительность или корректность?
Во-вторых, не обязательно "при выходе из скоупа".
Ну а когда RAII ещё срабатывает?
Да, я знаю, что умный указатель можно куда-то мувнуть, но это уже детали.
В третьих, вы опять придумываете гипотетические проблемы.
C++ приучил :(
давайте для начала вспомним что в чисто функциональном ЯП степень свободы изначально намного ниже и вводить/выдерживать контракты сильно проще. По факту если в плюсах писать в чисто функциональном стиле, пользуясь только иммутабельными объектами, вы тоже можете получить весьма надежный код.
Не понял, почему это?
Да, ФП прячет детали вроде аллокации памяти. Но кроме этого есть куча других ресурсов (от файлов до БД до чего-нибудь логического). Да и память там тоже можно (и рекомендуется) выделять явно, если её нужно сразу много для какой-нибудь большой строки.
Плюс, ФП ≠ иммутабельность. В этих наших хаскелях вполне есть локальная мутабельность в той же монаде
ST
(и типы позволяют её отслеживать и убеждаться, что мутабельность именно что локальная).Плюс, иммутабельность сама по себе не помогает убедиться, что скоупы действительно вложены. Без трюков с типами и в том же хаскеле возможны ошибки, аналогичные использованию файла после его закрытия, например, в плюсах.
Можно расширить функционал имеющейся абстракции, можно ввести новую, можно ввести новую и потом обернуть вместе с имеющейся. В общем, заниматься разработкой. Не понимаю где что и чему тут мешает.
Ничему не мешает, просто показывает, что SRP таки нарушается.
Antervis
03.11.2021 00:43+2Мы обсуждаем производительность или корректность?
мне нравится как вы даже переспросили о чем спор, причем всего лишь во втором комментарии после того, как к нему присоединились. Сильно лучше некоторых.
Обсуждается производительность и универсальность соответствующих инструментов. Моя позиция - GC не может быть в общем случае быстрее и ограничивается лишь одним видом ресурсов - памятью, оставляя остальные на ручное удаление программистом.
Ну а когда RAII ещё срабатывает?
когда мы удаляем объект любым способом. Например, я же не выхожу из скоупа когда делаю
optional<T>::operator=(nullopt)
, но эта операция удаляет вложенный объект при его наличии, вызывая его деструктор.C++ приучил :(
выдумывать теоретически возможные ошибки, чтобы потом их у себя искать - весьма неэффективный подход к написанию и отладке кода.
Не понял, почему это?
как минимум лайфтаймы объектов вычисляются околотривиально, если ограничиться одними лишь чистыми функциями, принимающими иммутабельные аргументы по значению.
Плюс, ФП ≠ иммутабельность. В этих наших хаскелях вполне есть локальная мутабельность ...
мутабельность в ФП это бекдор, противоречащий парадигме. Собственно, поэтому и имеет смысл её локально ограничивать.
Ничему не мешает, просто показывает, что SRP таки нарушается.
можете показать где конкретно нарушается принцип единственной ответственности? Вот хоть убей не вижу.
0xd34df00d
03.11.2021 02:42+1мне нравится как вы даже переспросили о чем спор, причем всего лишь во втором комментарии после того, как к нему присоединились. Сильно лучше некоторых.
Это был риторический вопрос.
Моя позиция — GC не может быть в общем случае быстрее и ограничивается лишь одним видом ресурсов — памятью, оставляя остальные на ручное удаление программистом.
Естественно, потому что любая последовательность событий освобождения ресурсов в GC может быть повторена вручную. Однако, вы оставляете тут за скобками вопрос потребных человеческих ресурсов. Не надо так.
когда мы удаляем объект любым способом. Например, я же не выхожу из скоупа когда делаю std::optional<T>::operator=, но эта операция удаляет вложенный объект при его наличии, вызывая его деструктор.
Часто встречаете такой код для
T ~ std::unique_ptr<T'>
илиmyDbConnection
?выдумывать теоретически возможные ошибки, чтобы потом их у себя искать — весьма неэффективный подход к написанию и отладке кода.
Именно, весьма неэффективный, поэтому я в итоге перешёл на хаскель и прочие вещества потяжелее. Но на плюсах писать код с хоть какой-то степенью осознанности и уверенности нельзя.
как минимум лайфтаймы объектов вычисляются околотривиально, если ограничиться одними лишь чистыми функциями, принимающими иммутабельные аргументы по значению.
Ну а вот в ФП с точки зрения перформанса объекты принимаются по ссылке, а не по значению (с точки зрения семантики это неразличимо в ФП, понятное дело). Плюс, что куда важнее, я не очень понимаю, как по значению передать, например, файл или соединение с БД. Что это вообще значит?
мутабельность в ФП это бекдор, противоречащий парадигме. Собственно, поэтому и имеет смысл её локально ограничивать.
За факт противоречия парадигме ничего не надо ограничивать. Ограничивать надо потому, что с мутабельностью сложнее рассуждать о коде. Если у вас весь код живёт в IO, то вы о нём сказать можете сильно меньше, чем если
функции чистыеэффекты в типах более точные.можете показать где конкретно нарушается принцип единственной ответственности? Вот хоть убей не вижу.
Там, где объект начинает отвечать и за абстракцию над fd, и за конкретный специальный случай обычных файлов на диске. Это разные вещи.
Antervis
03.11.2021 03:33+1Естественно, потому что любая последовательность событий освобождения ресурсов в GC может быть повторена вручную
а еще любая последовательность событий поверх аллоцировал/освободил является избыточной с точки зрения управления памятью. С++ позволяет мне этим ограничиться.
Однако, вы оставляете тут за скобками вопрос потребных человеческих ресурсов. Не надо так.
я искренне считаю что человеческий ресурс сравним, потому что с с++-специфичными ошибками я сталкиваюсь куда реже, чем с чисто логическими. Которые очевидно допустил бы и в других языках.
Часто встречаете такой код для
T ~ std::unique_ptr<T'>
илиmyDbConnection
?сформулируйте вопрос иначе
Но на плюсах писать код с хоть какой-то степенью осознанности и уверенности нельзя.
define "какой-то степенью" потому что большая часть спектра возможных интерпретаций противоречит моему опыту. Вот буквально сегодня столкнулся с "invalid memory address or nil pointer dereference" в go приложении. Значит ли это что писать на go с "какой-то степенью осознанности и уверенности" тоже нельзя? Но ведь такие ошибки бывают и в c#/java, что про них? "А если нету разницы, зачем платить больше?" (с).
Ну а вот в ФП с точки зрения перформанса объекты принимаются по ссылке, а не по значению (с точки зрения семантики это неразличимо в ФП, понятное дело)
не важно как передается объект когда существует контракт что функция либо эксклюзивно им владеет, либо не может менять.
Там, где объект начинает отвечать и за абстракцию над fd, и/или за конкретный специальный случай обычных файлов на диске. Это разные вещи.
я волен инкапсулировать объект любым удобным мне образом. Захочу - сделаю тонкую обертку над дескриптором. Захочу - это будет представление "файла на диске". Или "файла на диске и/или в памяти". Или "файла лежащего по абстрактному URI". SRE не нарушится ни в одном из случаев, это всего лишь разные уровни абстракции.
0xd34df00d
04.11.2021 02:33+2я искренне считаю что человеческий ресурс сравним, потому что с с++-специфичными ошибками я сталкиваюсь куда реже, чем с чисто логическими. Которые очевидно допустил бы и в других языках.
Зависит от языков.
Плюс, attention span у людей ограничен. Если мне нужно думать о плюсовых UB, то у меня остаётся меньше ресурсов на обдумывание логики.
сформулируйте вопрос иначе
Часто засовываете в
std::optional
всякиеunique_ptr
ы или соединения с БД, и не для ленивой инициализации, а для того, чтобы одинunique_ptr
потом заменить другим?Вот буквально сегодня столкнулся с "invalid memory address or nil pointer dereference" в go приложении. Значит ли это что писать на go с "какой-то степенью осознанности и уверенности" тоже нельзя?
На go и без этих ошибок нельзя, кнопки
!;=efilnrtu{}
сотрёте, пока будете печататьif err != nill { return err; }
или как там.Но ведь такие ошибки бывают и в c#/java, что про них?
- Как часто они там встречаются?
- У вас мир C#/Java ограничивается, что ли? Должно быть, это очень грустный мир.
не важно как передается объект когда существует контракт что функция либо эксклюзивно им владеет, либо не может менять.
Вы можете как-то сразу определиться со своими аргументами? То у вас все объекты передаются по значению, то это теперь неважно.
SRE не нарушится ни в одном из случаев, это всего лишь разные уровни абстракции.
SRP вообще имеет смысл-то так? А то ведь для любого описания функциональности есть уровень абстракции, где это единый юнит.
Antervis
04.11.2021 03:22+1Плюс, attention span у людей ограничен. Если мне нужно думать о плюсовых UB, то у меня остаётся меньше ресурсов на обдумывание логики.
Во-первых, это вопрос опыта разработки на конкретном языке, во-вторых, логика и реализация продумываются на разных этапах?
Часто засовываете в
std::optional
всякиеunique_ptr
ы или соединения с БД, и не для ленивой инициализации, а для того, чтобы одинunique_ptr
потом заменить другим?не часто, но важно ведь не это. Важно то, что деструктор сработает при любом удалении объекта, а не только при неявном выходе из скоупа (чего в общем-то можно добиться и всякими defer'ами, пусть и многословнее).
Как часто они там встречаются?
как часто null deference встречается в современных плюсах? В тех, где сырые указатели в принципе используются сильно реже ссылок и умных.
У вас мир C#/Java ограничивается, что ли? Должно быть, это очень грустный мир.
это самые яркие примеры языков общего назначения с GC, которые в остальном похожи на плюсы. Не с полумертвым D же сравнивать.
Вы можете как-то сразу определиться со своими аргументами? То у вас все объекты передаются по значению, то это теперь неважно.
С точки зрения ABI передача по значению в с++ это создание нового объекта и передача его в функцию по ссылке. Если для объекта гарантируются свойства иммутабельности и неотличимости копии от оригинала, то копирование можно опустить и сразу передавать объект по ссылке. Собственно, в функциональных языках оба эти свойства могут гарантироваться, поэтому и не принципиально как там называется способ передачи
SRP вообще имеет смысл-то так? А то ведь для любого описания функциональности есть уровень абстракции, где это единый юнит.
Во-первых, и уровень абстракции, и функционал определяются во время проектирования, и тогда же можно однозначно ответить нарушается SRE или нет. Просто вы начали делать предположения про гипотетическую абстракцию, которую я еще не завершил проектировать, приведенную мной лишь для примера.
А во-вторых, забываете что принципы SOLID это просто свод указаний, а не жестких законов.
0xd34df00d
04.11.2021 04:01+2Во-первых, это вопрос опыта разработки на конкретном языке
Ну вот моих 18 лет опыта, очевидно, недостаточно. Практика дырок в других приложениях показывает, что там тоже недостаточно.
во-вторых, логика и реализация продумываются на разных этапах?
Я вкусил запретный плод языков, помогающих разрабатывать и уточнять логику во время написания кода, и это очень вкусный плод.
не часто, но важно ведь не это. Важно то, что деструктор сработает при любом удалении объекта, а не только при неявном выходе из скоупа (чего в общем-то можно добиться и всякими defer'ами, пусть и многословнее).
Но только правила языка не гарантируют, что лайфтаймы объектов, зависящих друг от друга, будут соответствующим образом вложены, и в этом проблема.
как часто null deference встречается в современных плюсах? В тех, где сырые указатели в принципе используются сильно реже ссылок и умных.
А зачем вы спорите с соломенными чучелами? Давайте лучше сравним количество висячих ссылок, выходов за границы и так далее.
это самые яркие примеры языков общего назначения с GC, которые в остальном похожи на плюсы.
Зачем похожесть на плюсы? Императивно-нулловая философия накладывает свои ограничения.
С точки зрения ABI передача по значению в с++ это создание нового объекта и передача его в функцию по ссылке. Если для объекта гарантируются свойства иммутабельности и неотличимости копии от оригинала, то копирование можно опустить и сразу передавать объект по ссылке.
А если я его после этого сохранил как поле объекта?
А во-вторых, забываете что принципы SOLID это просто свод указаний, а не жестких законов.
Я не то что забываю, я изначально во всех этих солидах не шарю, там сложно чё-т слишком.
Antervis
04.11.2021 11:56+1Ну вот моих 18 лет опыта, очевидно, недостаточно. Практика дырок в других приложениях показывает, что там тоже недостаточно.
опыт коммерческой разработки с 12 лет то?
Но только правила языка не гарантируют, что лайфтаймы объектов, зависящих друг от друга, будут соответствующим образом вложены, и в этом проблема.
проблема лайфтаймов ортогональна GC и решается без него (как в том же расте)
А зачем вы спорите с соломенными чучелами? Давайте лучше сравним количество висячих ссылок, выходов за границы и так далее.
хотите сказать в managed языках не бывает выхода за границы?
Зачем похожесть на плюсы? Императивно-нулловая философия накладывает свои ограничения.
так вся дискуссия про то, насколько полезен GC. Соответственно желательно сравнивать языки, которые максимально близки во всем, кроме наличия/отсутствия GC.
А если я его после этого сохранил как поле объекта?
вариантов уйма же, самый простой - копирование.
Я не то что забываю, я изначально во всех этих солидах не шарю, там сложно чё-т слишком.
вы постоянно находите простейшие вещи невероятно сложными... И наоборот
0xd34df00d
04.11.2021 20:20+2опыт коммерческой разработки с 12 лет то?
Коммерческой — где-то с 16-17. Но не обязательно получать деньги, чтобы продуктивно наступать на грабли.
проблема лайфтаймов ортогональна GC и решается без него (как в том же расте)
Ну наконец-то вы сами начали подходить к тому, о чём я говорил изначально — о типах, то бишь.
хотите сказать в managed языках не бывает выхода за границы?
Хочу сказать, что в managed-языках без unsafe выход за границы отлавливается и не является UB.
так вся дискуссия про то, насколько полезен GC. Соответственно желательно сравнивать языки, которые максимально близки во всем, кроме наличия/отсутствия GC.
Нет. Имеет смысл рассматривать языки, которые без GC реализуются тяжко (функциональщину там всякую).
вариантов уйма же, самый простой — копирование.
Ну ещё раз, скопируйте мне соединение с БД.
Antervis
04.11.2021 21:27Ну наконец-то вы сами начали подходить к тому, о чём я говорил изначально — о типах, то бишь.
в дискуссии о полезности GC?
Хочу сказать, что в managed-языках без unsafe выход за границы отлавливается и не является UB.
Это всё еще ошибка, она всё еще допущена, код всё еще работает некорректно. Более того, для неожиданных исключений обычно отсутствует нормальная обработка. В итоге приложение приложение уходит в некоторое некорректное состояние, в котором может застрять до рестарта (недавно ловил такое). По мне так лучше краш + рестарт.
Так или иначе, от ошибки мы не избавились, просто получили возможность запихать её под ковер.
Нет. Имеет смысл рассматривать языки, которые без GC реализуются тяжко (функциональщину там всякую).
предлагаю вам найти более компетентного собеседника для споров про функциональщину. Я имею поверхностное представление о том, каким должен быть чистый функциональный язык, но не знаком со всем набором распространенных костылей и хаков, которые делают реальные функциональные языки применимыми.
Ну ещё раз, скопируйте мне соединение с БД.
shared_ptr<DBConnection> conn(otherConn);
?
0xd34df00d
04.11.2021 22:13+2в дискуссии о полезности GC?
В дискуссии о методах управления ресурсами.
Это всё еще ошибка, она всё еще допущена, код всё еще работает некорректно.
Нет, код не работает некорректно. Код вообще не работает, потому что его выполнение прервалось экзепшоном. Это в плюсах вы можете проехаться по памяти какого-нибудь другого массива и получить совершенно не имеющие смысла результаты расчётов. Поэтому, собственно
от ошибки мы не избавились, просто получили возможность запихать её под ковер.
— это про плюсы. Краш при некорректной работе с памятью в плюсах вам совершенно, вообще не гарантирован. Я благодарил плюсовых богов каждый раз, когда моя программа падала, а не молча продолжала выполнение.
shared_ptr<DBConnection> conn(otherConn);
Очень по значению, ага.
От того, что вы указатель завернули в класс и класс передаёте по значению, вся программа в «только по значению» не превратилась.
Antervis
04.11.2021 23:06В дискуссии о методах управления ресурсами.
GC не является "методом управления ресурсами", а только лишь памятью.
Нет, код не работает некорректно.
"Код не работает корректно", whatever. Вы пытаетесь продать исключение при выходе за границу так, словно оно предотвратило ошибку. Однако надежность системы не выросла ни на йоту.
Код вообще не работает, потому что его выполнение прервалось экзепшоном.
"Прервалось экзепшном" != "не работает". Максимально вероятно код будет продолжать работать каким-то не запланированным способом. Так же как и в плюсах. Но у вас почему-то в одном языке в выходе за границу массива будет виноват программист, а в другом языке - сам ЯП. Где в вашем сознании пролегает та самая эфемерная граница, их разделяющая? Возможно это банальная предвзятость?
Очень по значению, ага.
От того, что вы указатель завернули в класс и класс передаёте по значению, вся программа в «только по значению» не превратилась.
Я не понимаю чего вы от меня добиваетесь. Я ж говорю - передавать объекты в функции в абстрактном чисто функциональном языке можно практически как угодно. Вроде логично, что любой иммутабельный объект можно сделать refcounted, реализуя тем самым его копируемость.
0xd34df00d
05.11.2021 06:07+3GC не является "методом управления ресурсами", а только лишь памятью.
Да. Что не помешало вам вспомнить, что RAII позволяет не только это, и вы же написали:
RAII дает куда больше чем простое освобождение памяти.
На что, собственно, я и ответил, что плохо оно это даёт, типы лучше.
Вы пытаетесь продать исключение при выходе за границу так, словно оно предотвратило ошибку.
Именно так, см. ниже.
Однако надежность системы не выросла ни на йоту.
Нет, выросла.
Гарантии можно в первом приближении разложить в три уровня, в порядке ослабления:
- Статические гарантии корректности программы (соответствие спеке, отсутствие логических ошибок, статические проверки выхода за границы) — это всякие там завтипы, прочая подобная ерунда.
- Динамические гарантии корректности, когда программа завершается либо ответом, либо явной ошибкой, и можно доказать, что получение ответа автоматически исключает некоторый класс ошибок — это как раз и рантайм-проверки выхода за границы, и гипотетический логически неконсистентный как сегодня хаскель, но с завтипами.
- Никаких гарантий корректности, когда если программа выдала какой-то ответ, то никаких гарантий на самом деле нет — это как раз C++.
Где-то на вершине условные агды с идрисами, посередине — условный хаскель (и на самом деле он очень далеко от вершины, к слову), чуть ниже — условный питон, ещё ниже — C++.
Если у меня есть условный торговый бот, который рассчитывает, торговать или не торговать, то я бы предпочёл, чтобы при ошибке доступа за границы он упал, чем если бы он просто модифицировал соседний массив флоатов с весами для какой-нибудь регрессионной модели, которая после этого начнёт выдавать фигню.
"Прервалось экзепшном" != "не работает". Максимально вероятно код будет продолжать работать каким-то не запланированным способом.
Объясните для тупых, как код будет продолжать работать после того, как гипотетический
operator[]
кинет здесь ошибку?int ints[42] = {}; float floats[10] = {}; ints[42] = 0;
А если экзепшона нет, то вы просто попортите один из флоатов, если не повезёт с их раскладкой в памяти, и всё.
Так же как и в плюсах.
В плюсах оно не прерывается экзепшоном. Как это «так же»?
Где в вашем сознании пролегает та самая эфемерная граница, их разделяющая? Возможно это банальная предвзятость?
Это не граница, это спектр.
Вроде логично, что любой иммутабельный объект можно сделать refcounted, реализуя тем самым его копируемость.
Что вообще такое рефкаунт в иммутабельном случае? Как там счётчик выглядит, что вы модифицировать собрались?
eao197
05.11.2021 07:04Если у меня есть условный торговый бот, который рассчитывает, торговать или не торговать, то я бы предпочёл, чтобы при ошибке доступа за границы он упал, чем если бы он просто модифицировал соседний массив флоатов с весами для какой-нибудь регрессионной модели, которая после этого начнёт выдавать фигню.
А владелец этого торгового робота предпочел бы, чтобы этот робот делал свои расчеты быстрее роботов конкурентов. А динамические проверки в ран-тайме на скорость неизбежно скажутся и не лучшим образом. Посему пойдет ваш корректный, но небыстрый, робот туда же, куда и быстрый, но некорректный.
0xd34df00d
05.11.2021 08:37+2К счастью, кроме динамических проверок есть возможность что-то про код доказывать статически (про что я тоже писал).
Ну или замените торгового бота на программу, рассчитывающую дозу рентгеновского аппарата.
eao197
05.11.2021 08:49К счастью, кроме динамических проверок есть возможность что-то про код доказывать статически (про что я тоже писал).
И тогда рассматривать пример с динамической проверкой границ и выбросом исключения становится бессмысленным.
Ну или замените торгового бота на программу, рассчитывающую дозу рентгеновского аппарата.
Насколько я помню ту историю, там одной из проблем стало то, что разработчики не подумали о том, что пользователь может вводить данные на пульте с высокой скоростью. Что и приводило к появлению нового значения, которое не было учтено должным образом.
По поводу динамических проверок можно еще катастрофу Ариан-5 вспомнить, когда был переиспользован код из Ариан-4, не рассчитанный на условия взлета другой ракеты. Динамические проверки сработали, но надежности это не добавило. Потому что ошибка была на другом уровне.
И да, софт для Ариан-5 был не на C++, а на более безопасной Ада.
0xd34df00d
05.11.2021 09:13+1И тогда рассматривать пример с динамической проверкой границ и выбросом исключения становится бессмысленным.
К сожалению, эта возможность есть не во всех языках, поэтому приходится идти на компромиссы.
Динамические проверки сработали, но надежности это не добавило. Потому что ошибка была на другом уровне.
Я бы сказал, что для вещей вроде ракет, когда остановиться и подумать нет времени, динамические проверки — не очень хорошая идея и так.
Antervis
05.11.2021 18:55-1На что, собственно, я и ответил, что плохо оно это даёт, типы лучше.
т.е. по свойству транзитивности типы лучше GC?
Если у меня есть условный торговый бот
а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена. АЭС конечно же редкий случай, но если проект может позволить себе failsafe, то опять же варианты 2 и 3 практически иррелевантны.
Объясните для тупых, как код будет продолжать работать после того, как гипотетический
operator[]
кинет здесь ошибку?исключение долетит до ближайшего хендлера и продолжит выполнение оттуда. Например, сервер вернет 500-ку. Вот только т.к. никто не почистил состояние после возникновения ошибки, этот выход за границы повторится и для следующих запросов. Что хуже - один битый запрос, или миллионы?
Тем не менее я не хочу меряться чей вырожденный негативный сценарий хуже. Я лишь пытаюсь объяснить что возможность обработки ошибки сама по себе не помогает её предотвратить.
В плюсах оно не прерывается экзепшоном. Как это «так же»?
пишите at, будет прерываться.
Это не граница, это спектр.
где-то я это уже слышал. Правда, тот раздел психологии перестали преподавать в вузах.
Что вообще такое рефкаунт в иммутабельном случае? Как там счётчик выглядит, что вы модифицировать собрались?
рефкаунтинг всегда делают "под капотом", так, что снаружи выглядит словно инстанс не меняется. И я всё еще не понимаю чего вы от меня добиваетесь. Хотите чтобы я спроектировал функциональный язык без GC?
ldss
05.11.2021 20:29+1а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена.
почему? Если у меня нормальная стратегия обработки ексепшенов, я при вылете оного спокойненько аварийно заглушу станцию
А если ошибка есть, но никак себя не проявила - станция подвзорвется, даже при наличии стратегии обработки ошибок - потому что обрабатывать и реагировать не на что
Antervis
05.11.2021 21:12почему? Если у меня нормальная стратегия обработки ексепшенов, я при вылете оного спокойненько аварийно заглушу станцию
Это если вы предусмотрели "нормальную стратегию обработки" исключений конкретного вида, сигнализирующих о кейсе, который вы явно не предусмотрели. Чувствуете противоречие?
Antervis
05.11.2021 21:32вопрос-то в том, что во втором случае пофигу, есть она у меня или нет
я уже дважды привел кейс где объяснял насколько это совершенно не "пофигу"
ldss
05.11.2021 21:37+1Ну давайте сначала
Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок
И возникает ситуация, описанная выше по треду
В одном случае программа упадет с ошибкой (в силу особенностей языка), во втором нет (опять же, в силу особенностей языка) - продолжит работать, но уже некорректно.
Т.е., есть во втором случае есть у вас обработка ошибок, нет ее - совершенно никак вам не поможет
Antervis
05.11.2021 21:48Ну давайте сначала
хорошо, в четвертый раз...
Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок
так я и утверждаю что её нет! Вы либо предусмотрели возможность ошибки типа X (в нашем случае выхода за границы массива), добавили доп. условие и предотвратили возникновение этой ошибки. Либо вы эту ошибку не предусмотрели, и соответственно забыли добавить для неё корректную обработку. И тогда лучшее, на что вы можете надеяться - что ошибка типа X улетит в обработчик ошибки типа Y, который волшебным образом сделает что-то отдаленно логичное для конкретной ситуации. В худшем случае последствия будут печальнее проезда по памяти.
0xd34df00d
06.11.2021 00:20+2т.е. по свойству транзитивности типы лучше GC?
Не понял, причём тут транзитивность?
а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны — после катастрофы задача "не допустить сбой" провалена. АЭС конечно же редкий случай, но если проект может позволить себе failsafe, то опять же варианты 2 и 3 практически иррелевантны.
На софтовом уровне в АЭС failsafe вполне себе есть: можно просто взять и заглушить реактор (например, просто подняв или опустив стержни, в зависимости от конструкции). Да, неприятно, да, йодная яма, но не взрыв.
Поэтому если в процессе расчёта, не знаю, потребной скорости движения воды вы там случайно что-то не то сделали, то лучше не закрыть клапаны и всё перегреть, а тормознуть реактор.
исключение долетит до ближайшего хендлера и продолжит выполнение оттуда. Например, сервер вернет 500-ку.
500-ка — это не то же самое, что «мы тут рассчитали какую-то ерунду и вернули её», не так ли?
Вот только т.к. никто не почистил состояние после возникновения ошибки, этот выход за границы повторится и для следующих запросов.
А в случае недетектируемого выхода за границы это не так? Это тем более так — вы просто будете выдавать аналогичную ерунду на последующие запросы, так как условия совершенно аналогично не поменялись.
Что хуже — один битый запрос, или миллионы?
- С чего он будет один?
- Вопрос некорректен: что лучше, M битых ответов с данными, взятыми из пятой точки, или N ответов «честно признаюсь, что не шмогла»?
Я лишь пытаюсь объяснить что возможность обработки ошибки сама по себе не помогает её предотвратить.
Она помогает узнать, что ошибка вообще была.
пишите at, будет прерываться.
…и все свои зависимости надо проверить, что там at.
рефкаунтинг всегда делают "под капотом", так, что снаружи выглядит словно инстанс не меняется. И я всё еще не понимаю чего вы от меня добиваетесь. Хотите чтобы я спроектировал функциональный язык без GC?
Нет, я хочу, чтобы вы определились, понимаете ли вы, как работает чистое ФП (и не предлагали рефкаунты под капотом), или же не понимаете (и не пытались сказать, что ФП — это семантически C++, где всё pass-by-value).
Antervis
06.11.2021 08:18500-ка — это не то же самое, что «мы тут рассчитали какую-то ерунду и вернули её», не так ли?
я пытаюсь доказать что ошибки обоих видов могут вести к довольно-таки неприятным последствиям. Вы же пытаетесь доказать что гипотетическая программа A на языке X, содержащая ошибку W, обязательно обработает эту ошибку хуже гипотетической программы B на языке Y. Совершенно игнорируя тот факт что это зависит не от языков X vs Y, а от программ A vs B. И я уже объяснил почему язык B не поможет предотвратить ошибку W сколь угодно лучше языка A.
Давайте если продолжать спор, то вы не будете пытаться доказать что-то заведомо абсурдное.
Нет, я хочу, чтобы вы определились, понимаете ли вы, как работает чистое ФП (и не предлагали рефкаунты под капотом), или же не понимаете (и не пытались сказать, что ФП — это семантически C++, где всё pass-by-value).
во-первых, GC в функциональных языках по-вашему на пыльце фей работает? Там такие же счетчики ссылок под капотом. Во-вторых, вы придираетесь к той части, которая мне безынтересна. Мой аргумент состоял в том, что за счет более жестких контрактов в ФП можно обеспечить некоторые дополнительные гарантии. Ваши контраргументы это не опровергают... и вообще непонятно зачем они.
0xd34df00d
08.11.2021 00:29+2я пытаюсь доказать что ошибки обоих видов могут вести к довольно-таки неприятным последствиям. Вы же пытаетесь доказать что гипотетическая программа A на языке X, содержащая ошибку W, обязательно обработает эту ошибку хуже гипотетической программы B на языке Y.
Нет, не обязательно, конечно. Вы спорите с соломенным чучелом. Легко представить себе программу на C#, которая выдаёт ерунду чаще, чем программу на VB со включённым (и удобно обсуждаемым в соседней статье) on error resume next.
Только вот плюсовое UB — это как раз VBшное on error resume next.
А то, что я пытаюсь сказать — это что диагностируемые ошибки лучше недиагностируемых и скрытых.
во-первых, GC в функциональных языках по-вашему на пыльце фей работает?
Это деталь реализации, при обсуждении операционной (или денотационной, неважно) семантики про подобное вспоминать не имеет смысла.
Мой аргумент состоял в том, что за счет более жестких контрактов в ФП можно обеспечить некоторые дополнительные гарантии. Ваши контраргументы это не опровергают… и вообще непонятно зачем они.
Вы изначально начали говорить, что в ФП за счёт иммутабельности что-то там происходит, и что вот если на плюсах писать в чистом иммутабельном стиле с передачей объектов по значению, то с лайфтаймами и скоупами будет хорошо. Я говорю, что нет, не будет (хотя бы потому, что на C++ нельзя написать нетривиальную программу в иммутабельном стиле с передачей объектов по значению).
Antervis
08.11.2021 01:55Вы спорите с соломенным чучелом.
А "условного торгового бота" тоже я выдумал?
А то, что я пытаюсь сказать — это что диагностируемые ошибки лучше недиагностируемых и скрытых.
В общем случае да, я говорю про частный - когда задача не диагностировать ошибку, а предотвратить её возникновение. По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.
Это деталь реализации, при обсуждении операционной (или денотационной, неважно) семантики про подобное вспоминать не имеет смысла.
я лишь утверждал что любой иммутабельный объект можно передать по копии, даже если для этого придется заложить рефкаунтинг под капот. Не надо пожалуйста заводить спор а потом говорить что парировать ваши аргументы в нём не имеет смысла.
Вы изначально начали говорить, что в ФП за счёт иммутабельности что-то там происходит
не происходит, а гарантируется
... и что вот если на плюсах писать в чистом иммутабельном стиле с передачей объектов по значению, то с лайфтаймами и скоупами будет хорошо. Я говорю, что нет, не будет (хотя бы потому, что на C++ нельзя написать нетривиальную программу в иммутабельном стиле с передачей объектов по значению).
почему нельзя написать то?
0xd34df00d
09.11.2021 00:40+1А "условного торгового бота" тоже я выдумал?
Нет, конечно. Но это пример, призванный иллюстрировать, чем непредсказанные во время разработки, но пойманные во время выполнения ошибки лучше непойманных.
Про обязательность обработки «хуже» я ничего не говорил и даже не имел в виду.
В общем случае да, я говорю про частный — когда задача не диагностировать ошибку, а предотвратить её возникновение. По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.
В таком случае нам что плюсы, что всякие джавы-дотнеты вообще обсуждать не стоит изначально. Но так как мы таки их обсуждаем, то этот частный случай как-то тут не к месту рассматривать, ИМХО.
я лишь утверждал что любой иммутабельный объект можно передать по копии, даже если для этого придется заложить рефкаунтинг под капот.
Если язык позволяет вам выразить рефкаунт, то это уже не язык с одними лишь иммутабельными объектами, только и всего.
почему нельзя написать то?
Потому что рефкаунт не будет иммутабельным.
Antervis
09.11.2021 07:57Нет, конечно. Но это пример, призванный иллюстрировать, чем непредсказанные во время разработки, но пойманные во время выполнения ошибки лучше непойманных.
Но так как мы таки их обсуждаем, то этот частный случай как-то тут не к месту рассматривать, ИМХО.
Вы как-то очень избирательно решаете рассматривать или не рассматривать частные случаи. Начинает даже казаться, что критерием является поддержка аргументом вашей точки зрения. Но давайте всё-таки попробуем провести объективную ревизию того, что мы можем игнорировать, а что - нет.
Вот вы описали все ужасы порчи памяти, а я продемонстрировал, как некорректная обработка ошибки может усугубить её последствия. Это гипотетические worst case scenario, основанные на предположениях о конкретном поведении конкретного кода. В целом можно игнорировать, верно?
Далее, вы утверждали диагностируемые ошибки строго лучше UB. С этим я не спорю. Но вы так же утверждали что это улучшает надежность системы, с чем я не согласен по тому определению надежности, которое я привел выше. Почему это частный случай и его не имеет смысл рассматривать?
Если язык позволяет вам выразить рефкаунт, то это уже не язык с одними лишь иммутабельными объектами, только и всего.
Я ж говорю не про язык, а про компилятор/рантайм. Рефкаунт может служить лишь как оптимизация копирования, применимая для всех иммутабельных объектов. Но я всё еще не понимаю к чему это всё. Изначально я утверждал, что в функциональных языках более строгие контракты и можно добиться более строгих гарантий. Если вы пытаетесь спорить с этим утверждением, то я не могу понять как ваши аргументы его опровергают.
0xd34df00d
11.11.2021 09:31+1Вот вы описали все ужасы порчи памяти, а я продемонстрировал, как некорректная обработка ошибки может усугубить её последствия.
Я так и не понял, что именно усугубляется, когда вместо некорректных расчётов вы получаете ошибку 500.
Но вы так же утверждали что это улучшает надежность системы, с чем я не согласен по тому определению надежности, которое я привел выше. Почему это частный случай и его не имеет смысл рассматривать?
Потому что для предотвращения ошибок в компилтайме плюсы и условные дотнеты подходят примерно одинаково хреново.
Рефкаунт может служить лишь как оптимизация копирования, применимая для всех иммутабельных объектов.
Не «лишь как». Некоторые объекты принципиально некопируемы, вроде тех же несчастных соединений с БД.
eao197
05.11.2021 07:01+2Однако надежность системы не выросла ни на йоту.
Тут вы не правы. Надежность определяется как тем, делает ли программа какую-то незапланированную фигню (выходит за пределы массива), так и тем, диагностируется ли эта незапланированная фигня или же нет (отсутствие диагностики как раз снижает надежность), так и тем, есть возможность среагировать на диагностику.
Как раз динамические проверки в ран-тайме с выбросом исключения, на которое можно среагировать, и дают возможность простым смертным разрабатывать надежные программы.
Другое дело, что в реальной жизни в областях, где все еще применяется C++, мы вынуждены искать компромисс между производительностью и надежностью, с приоритетом, таки, на производительности. На надежности это сказывается гораздо хуже, чем в языках, где производительность не самое главное.
Antervis
05.11.2021 19:03Надежность определяется как тем, делает ли программа какую-то незапланированную фигню (выходит за пределы массива), так и тем, диагностируется ли эта незапланированная фигня или же нет (отсутствие диагностики как раз снижает надежность), так и тем, есть возможность среагировать на диагностику.
ну давайте рассмотрим такой сценарий. Мы хотим предотвратить ошибки с помощью их диагностики в предварительных прогонах. Например, в тестах. Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?
На надежности это сказывается гораздо хуже, чем в языках, где производительность не самое главное.
в тех языках страдает еще и применимость. Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?
eao197
05.11.2021 20:06+1Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?
Тут важнее другое: а какие гарантии нам это дает?
Момент первый. Вроде как еще никому не удавалось что-то более менее серьезное протестировать на всех возможных входных данных во всех возможных ситуациях.
Момент второй. В некоторых областях накладные расходы, которые мы вынуждены нести на санитайзеры или даже на отсутствие должных оптимизаций в debug-режимах, могут вести к существенному изменению т.н. "времянки". Из-за чего под санитайзерами просто не будут возникать те приключения, которые у нас возникнут при полной скорости работы.
Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?
У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу. Ведь, как оказалось, где-то есть инструменты поудобнее. И решать на них какие-то задачи проще, чем на C++. Боль от такого открытия и заставляет 0xd34df00d снова и снова сравнивать C++ с другими языками.
Antervis
05.11.2021 20:29У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу.
и в итоге зачастую сравнивает с++98/03 с языками, появившимися после с++11/14. Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного. То есть аргументы вроде как правильные, но они рисуют картину, которая от реальной разработки на современных плюсах отличается как небо и земля.
ldss
05.11.2021 21:16Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного
я после 8 лет на плюсах начал писать еще на c# 1.1
Радовался как дитяА когда 2й вышел, так и вообще стало казаться, что больше уже ничего не надо
А когда вышел 3й, стало ну совершено понятно, что с++ это забытое прошлое:)
Antervis
05.11.2021 21:42+1я после 8 лет на плюсах начал писать еще на c# 1.1
Радовался как дитято есть вы сейчас расписались в той же самой ошибке, за которую я критикую 0xd34df00d, и которая явно является базой вашей предвзятости.
Antervis
08.11.2021 22:16+3так как вы можете судить об ошибках, если ни на чем, кроме плюсов не пишете?
давайте еще раз. Вот вы в комментах к теме, посвященной си и с++, утверждаете, что понимаете состояние дел с++, на котором вы не писали лет 20, лучше, чем человек, который только на них и пишет, аргументируя это тем, что вышеупомянутый не пишет на других языках? Всех эпитетов моего лексикона не хватит для описания абсурдности этого утверждения.
ldss
09.11.2021 22:27-1так я последний раз пару лет назад писал, как раз с использованием разного - unique_ptr и прочего
Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.
А у вас, судя по всему, подобного опыта нет. Как там было, "узкий специалист подобен флюсу - полнота его одностороння"(ц)
По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.
ыы
вы, наверное, не очень давно работаете, извините
непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей
Antervis
10.11.2021 00:15Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.
на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов. Да, вроде как не беспокоишься о лайфтаймах, но почти любая опечатка ловится только в рантайме. Спасибо, я лучше на плюсах буду писать, нервы дороже.
вы, наверное, не очень давно работаете, извините
что-то около 6-7 лет, наверно не очень давно, да. За выслугу лет еще не награждали.
непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей
а если я бекенд разрабатываю, то кто есть пользователь? А если ошибки бывают, но в большинстве чисто логические, которые были бы допущены и в других языках?
ldss
15.11.2021 18:12на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов.
Зачем?
а если я бекенд разрабатываю, то кто есть пользователь?
как только у вас появляется хотя бы пару тысяч пользователей, все становится интересно. Потому что то, что они могут учудить, ни одному QA в голову не придет
Так что ошибки/баги есть всегда; рассуждения о том, что надо писать без них - идеалистические
F0iL Автор
10.11.2021 00:40+4Прошу прощения, что встреваю в ваш спор, но вот он я. Не люблю меняться пиписьками, но раз уж зашёл разговор про опыт, скажу что за 15 лет профессиональной деятельности в IT кроме плюсов и сей я писал ещё в разное время на Delphi (упокой господь его душу), PHP (но старом, до 5ой версии), Python (в основном всякую CI/CD автоматизацию), JS/TS (и фронтенд и бэкенд), C# (не то чтобы много, но и немало, под десктоп и под ASP.net), на Go, чуть-чуть на Java, и ещё на паре языков, названия которых вы наверное даже не слышали. Поэтому мне есть с чем сравнивать, но я читаю ваши комментарии и искренне не могу понять, что и кому вы тут пытаетесь доказать.
То что кроме плюсов есть языки более дружелюбные к программисту и позволяющие меньше напрягать мозг? Да с этим никто и не спорит в общем-то, это и так очевидно.
То что плюсы с годами и новыми стандартами не становятся лучше и приятнее? Я с ними работаю каждый день и вижу своими глазами обратное. Очень даже становятся.
То что вам не нравятся плюсы, вы на них не пишете и не хотите писать? Да вас никто не заставляет так-то, не хотите - не пишите.
То что все остальные должны срочно перестать писать на плюсах? Извините, нет. На плюсах есть много офигенных и интересных проектов. Прошивки IoT- и телеком-железок пишутся на плюсах. Движки веб-браузеров и виртуальные машины пишутся на плюсах. Графические движки пишутся на плюсах. И очень много чего ещё пишется на плюсах. Поэтому я тоже на них пишу и мне нравится :)
ldss
15.11.2021 18:17+1То что кроме плюсов есть языки более дружелюбные к программисту и позволяющие меньше напрягать мозг?
Меньше напрягать мозг на особенности языка. И эти высвободившиеся мощности можно вполне пустить на бизнес-логику. Это, на мой взгляд, эффективнее - все таки программы на работе мы пишем не для себя, a для пользователей, а им пофиг на язык
То что плюсы с годами и новыми стандартами не становятся лучше и приятнее? Я с ними работаю каждый день и вижу своими глазами обратное. Очень даже становятся.
из того, что я вижу в обсуждении - они становятся все сложнее и с б0льшим UB
То что все остальные должны срочно перестать писать на плюсах?
Так оно само так случится, очень уж высокий порог входа и дорогая поддержка
Графические движки пишутся на плюсах
Например, Unity:))
ldss
17.11.2021 17:52и на плюсах, и на с#
но вся разработка с ним - на c#
Если с++ так чудесен, почему бы не сделать его с++ only, как тот же unreal?
Aldrog
17.11.2021 19:05Вам говорят
Графические движки пишутся на плюсах
А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!
То, что скрипты игровой логики к нему пишутся на C# совершенно неважно, так как это уже другая область, в которой вообще вроде бы Lua доминирует.
ldss
17.11.2021 19:38+1А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!
Ок, ок, ошибся
Core у него на плюсах, все остальное - на сишарпе
То, что скрипты игровой логики к нему пишутся на C# совершенно неважно
скрипты пишутся на lua:)
А игра, т.е. взаимодействие логики отображения и взаимодействия - на с#; движок просто рисует меши, грубо говоря
F0iL Автор
15.11.2021 22:03+1Так оно само так случится, очень уж высокий порог входа и дорогая поддержка
Вы почему-то упустили слово "срочно" :) На самом деле нытьё про то что все массово откажутся от плюсов я слышу уже лет так 15, но не смотря на активное развитие другие языков за эти годы, не смотря на рост вычислительных мощностей, не смотря на приток огромного количества новых людей в индустрию, чьи мозги не были травмированы олдскульным кодингом, в большинстве упомянутых мною сфер плюсы по-прежнему лидируют и даже понемногу проникают в смежные.
Так что оно случится рано или поздно, но явно не в ближайший десяток лет. Скорее всего в большинстве упомянутых применений их вытеснит Rust, что в общем-то неплохо, но идти этот процесс будет очень медленно.из того, что я вижу в обсуждении - они становятся все сложнее и с б0льшим UB
Вот в этом и основная разница между мной и вами. Вы теоретизируете о чем-то что где-то услышали, а я делаю выводы исходя из того с чем я имею дело каждый рабочий день.
Например, Unity:))
Вы, должно быть, хотели блеснуть остроумием, но я вас расстрою - движок Unity написан на C++, шарп там используется только для вытаскивания апишек движка наружу.
ldss
17.11.2021 17:57в большинстве упомянутых мною сфер плюсы по-прежнему лидируют и даже понемногу проникают в смежные.
Ну вы посмотрите на тренд за последние лет 10-15
Использование плюсов неуклонно снижается. Да, в ембеддед они рулят и педалят, но это ж узкая область. Я, как дев, туда бы ни за что не пошел - есличо, работу будешь полгода-год искать (ну, у нас)
Вы теоретизируете о чем-то что где-то услышали
Я всего лишь читаю данную дискуссию
Вы, должно быть, хотели блеснуть остроумием, но я вас расстрою - движок Unity написан на C++, шарп там используется только для вытаскивания апишек движка наружу.
только?:) Т.е., то, что основной интерфейс для работы с юнити это с#, это только?
Antervis
17.11.2021 20:14Ну вы посмотрите на тренд за последние лет 10-15
Использование плюсов неуклонно снижается.
так уж прям неуклонно снижается? По данным stackoverflow доля с++ опять же заметно выросла за последние лет пять.
ldss
17.11.2021 21:30я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко
ну и в вашей ссылке речь о С :)
Я, вопщем, к тому, что не зацикливайтесь вы на одном с++ - это прекрасный язык, чтоб сломать себе голову и выстрелить в ногу, или устроить бурное обсуждение на хабре, курощая старпера который его уже почти забыл
Но имхо, язык программирования должен быть средством, упрощающим выражение бизнес логики, а не препятствием которое надо бороть.
И масса современных языков делает это лучше
Antervis
17.11.2021 22:36+2я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко
а потом мощности компьютеров стали позволять писать даже на языках с GC
ну и в вашей ссылке речь о С :)
вчитайтесь
Но имхо, язык программирования должен быть средством, упрощающим выражение бизнес логики, а не препятствием которое надо бороть.
Во-первых, с++ сильно производительнее большинства из этой массы современных языков. Экономить ресурсы на высокоуровневом ЯП куда сложнее, например, память в джаве или cpu в питоне. Во-вторых, я же никогда не пытаюсь доказать что с++ - самый безопасный и самый выразительный язык. Мой посыл как раз в том, что с годами он стал сильно удобнее и дал больше средств для обеспечения надежности. Настолько, что размен производительности на выразительность/безопасность становится куда менее очевидным. Большая часть ошибок, с которыми лично мне доводится иметь дело, чисто логические, которые я или мои коллеги допустили бы в любых ЯП. Что до меньшей части - ну буду я не дебажить краши, а оптимизировать боттлнеки, в общем случае это не быстрее.
ldss
18.11.2021 22:12а потом мощности компьютеров стали позволять писать даже на языках с GC
Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа
Во-первых, с++ сильно производительнее большинства из этой массы современных языков.
по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее; да и скорость выделения в managed/unmanaged heap мы уже обсуждали
Мой посыл как раз в том, что с годами он стал сильно удобнее и дал больше средств для обеспечения надежности
другие-то тоже на месте не стояли:)
вопщем, с моего имха я б не замыкался в рамках одного языка
Antervis
18.11.2021 23:06Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа
в частном случае да, в общем - нет. По факту совсем не париться о производительности обычно могут позволить себе только фронтендеры (потому что за железо платит пользователь) и ML специалисты (потому что их код всего лишь вызывает тяжелую обработку на с++). Ну либо если проект маленький и бесперспективный.
по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее;
класс приложений-"числодробилок" куда меньше класса приложений, работающих с текстом.
да и скорость выделения в managed/unmanaged heap мы уже обсуждали
и кажется вы из этого обсуждения ничего не вынесли
другие-то тоже на месте не стояли:)
ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...
вопщем, с моего имха я б не замыкался в рамках одного языка
да, я всё не могу выделить время поучить раст. А то даже глупо знать про UB в языке больше чем про синтаксис...
ldss
19.11.2021 01:30в частном случае да, в общем - нет
наоборот
В общем случае железо дешевле
По факту совсем не париться о производительности обычно могут позволить себе только фронтендеры (потому что за железо платит пользователь) и ML специалисты
ну-ну
наша контора продает, например, десктопный софт, который стоит (в полном сборе) 40000 баксов за 1 (одно) рабочее место. Даже супермощный десктопный комп будет стоить сильно меньшеЕсли же речь об онлайне, там еще проще, там амазоновское облако, например, которое отлично скейлится за очень небольшие деньги
и кажется вы из этого обсуждения ничего не вынесли
мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете
ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...
а в дотнете они появились 14 лет назад
да, я всё не могу выделить время поучить раст
смотрите, потом поздно будет
Antervis
19.11.2021 11:24наоборот В общем случае железо дешевле
как у вас частное перестало быть подмножеством общего?
наша контора продает, например, десктопный софт, ...
и этот частный пример по-вашему задает общие тенденции?
Если же речь об онлайне, там еще проще, там амазоновское облако, например, которое отлично скейлится за очень небольшие деньги
как раз там линейная зависимость стоимости аренды от потребляемых ресурсов. В зависимости от дистанции и масштабов проекта эта стоимость может дорастать до весьма существенной.
мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете
вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением
ldss
19.11.2021 21:37как у вас частное перестало быть подмножеством общего?
потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов
как раз там линейная зависимость стоимости аренды от потребляемых ресурсов. В зависимости от дистанции и масштабов проекта эта стоимость может дорастать до весьма существенной.
Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку
вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением
смотря что понимать под ресурсами
если говорить только о железе, то да
Если комплексно о разработке, то сильно экономит ресурсы; человеческие, в первую очередь
Antervis
19.11.2021 22:09потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов
"в среднем" - может быть, "в общем" - нет.
Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку
опять же, это "иногда" а не "всегда".
Если комплексно о разработке, то сильно экономит ресурсы; человеческие, в первую очередь
ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах. Чтобы достигнуть такого уровня производительности на с++ даже задумываться не приходится. Как думаете, сколько человеческих ресурсов мне и моим коллегам бы сэкономило если бы всё просто работало?
ldss
20.11.2021 01:20"в среднем" - может быть, "в общем" - нет.
в среднем и чаще всего
Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"опять же, это "иногда" а не "всегда".
да почти всегда
доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле
ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах.
так тут либо а) прокся просто криво написана б) либо да, затык по производительности. Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра
При чем тут язык?
Antervis
20.11.2021 03:31Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"
откуда вы взяли цифру в 90%?
доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле
а с чего вы взяли что доп разраб обязательно потребуется? И с чего вы взяли что он не окупится? И с чего вы взяли что дешевле? Из вашего конкретного случая или из головы?
так тут либо а) прокся просто криво написана б) либо да, затык по производительности.
криво или нет - не мне судить, я лишь знаю что прокся средней кривости на с++ должна выдержать 2к рпс без нареканий.
Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра
ну то есть вы предполагаете что надо было потратить больше времени на оптимизацию, да? Или просто закидать железом с горкой?
ldss
21.11.2021 23:47откуда вы взяли цифру в 90%?
да вакансии посмотрите на работных сайтах
а с чего вы взяли что доп разраб обязательно потребуется?
а они всегда требуются, если мы хотим чего-то большего
Если делаешь коробку, например, на доп улучшения надо кого-то нанимать, т.к. остальные уже заняты - новые фичи, апгрейды и прочее, запланированное менеджментом, и отложить их нельзя
ну то есть вы предполагаете что надо было потратить больше времени на оптимизацию, да? Или просто закидать железом с горкой?
если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст
Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин
Antervis
22.11.2021 01:09Если делаешь коробку, например, на доп улучшения надо кого-то нанимать, т.к. остальные уже заняты - новые фичи, апгрейды и прочее, запланированное менеджментом, и отложить их нельзя
так рождается софт, который потом приходится переписывать с нуля.
если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст
вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно
Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин
когда нагрузка несущественная или распределяется идеально - тогда да, мб и бьется...
ldss
23.11.2021 06:11так рождается софт, который потом приходится переписывать с нуля.
увы, это реалии отрасли
вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно
эээ.. это, мягко говоря, слабо обоснованное умозаключение
когда нагрузка несущественная или распределяется идеально - тогда да, мб и бьется...
эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске
Antervis
23.11.2021 10:14увы, это реалии отрасли
разница в том, перепишут вашу софтину с нуля через два года или через десять.
эээ.. это, мягко говоря, слабо обоснованное умозаключение
Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.
А конкурентные GC вводят дополнительные синхронизации потоков, стоимость которых снова квадратно пропорциональна числу активных потоков.
И в обоих случаях стоимость GC не является пренебрежимо малой даже на малом числе потоков. Выглядит как формальное доказательство.
эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске
я имел в виду распределение нагрузки между виртуальными машинами.
ldss
24.11.2021 18:02разница в том, перепишут вашу софтину с нуля через два года или через десять.
кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать
Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.
это, снова-здорово, если у нас вдруг перестало хватать памяти и запустился gc. А памяти у нас много, как уже было сказано
Antervis
24.11.2021 18:10кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать
так у вас софтина не работает как надо, клиенты перетекают к конкурентам, поток бабла постепенно снижается, если вовремя не переписать бабло закончится. Это с точки зрения бизнеса.
С точки зрения отдельного разработчика конечно же интереснее из раза в раз писать поделия-однодневки, постепенно обогащая резюме мертвыми проектами.
это, снова-здорово, если у нас вдруг перестало хватать памяти и запустился gc. А памяти у нас много, как уже было сказано
может быть и много, но никогда не бесконечно. Рано или поздно GC запустится, и чем больше памяти надо почистить, тем дороже цикл сборки мусора.
ldss
25.11.2021 22:54так у вас софтина не работает как надо, клиенты перетекают к конкурентам
это если они есть, и у них не хуже
Обычно, у них такие же проблемы. Большинство софта, который на рынке давно и даже стандарт - редкостное г в плане кода, там просто чюдовищный легаси
С точки зрения отдельного разработчика конечно же интереснее из раза в раз писать поделия-однодневки
поделия очень разные бывают. Можно пилить сайты, когда вся работа сведется к перекладыванию json-ов из таблички в табличку, а может и нет. Я вот, помню, работал на одну офшорку - ни одной одинаковой задачи не было за 2 года. Может, повезло, конечно
может быть и много, но никогда не бесконечно
ну так и нормально написанное приложение не увеличивает память постоянно
Antervis
26.11.2021 05:14Обычно, у них такие же проблемы
а если у кого-то из конкурентов это не так, можно сразу сворачиваться?
ну так и нормально написанное приложение не увеличивает память постоянно
в процессе работы "нормально написанного приложения" постоянно создаются и удаляются временные объекты, а под них нужно выделять память. И если эту память не очищать, она будет непрерывно расти до инициации цикла сборки мусора.
Можно пытаться выделять все объекты/буферы заранее, а потом работать уже с ними. Но, опять же, не в языках с GC
0xd34df00d
06.11.2021 00:41+1и в итоге зачастую сравнивает с++98/03 с языками, появившимися после с++11/14
Я не сравниваю C++98/03. Я сравниваю C++17/20.
Или в вашей версии C++20 что-то принципиально поменялось по части гарантий со стороны языка?
То есть аргументы вроде как правильные, но они рисуют картину, которая от реальной разработки на современных плюсах отличается как небо и земля.
Неа, не отличается. На C++20 писать в прод мне не приходилось, но на C++17 — вполне.
0xd34df00d
06.11.2021 00:39+1Мы хотим предотвратить ошибки с помощью их диагностики в предварительных прогонах. Например, в тестах. Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?
То, что нет никаких гарантий распечатывания всех ошибок памяти.
Я уж не говорю о логистических проблемах сборки всех зависимостей с санитайзерами, о повышенном потреблении ресурсов, о том, что некоторые ошибки проявляются только сильно спустя время, и так далее.
Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?
Почему неприменим-то?
Antervis
06.11.2021 08:34+1Неа, не отличается. На C++20 писать в прод мне не приходилось, но на C++17 — вполне.
расскажите подробнее. Сколько, объем проекта, какого размера команда, насколько там современный код?
Или в вашей версии C++20 что-то принципиально поменялось по части гарантий со стороны языка?
начиная с с++11 появляется всё больше инструментов, позволяющих решать задачи быстрее и надежнее. Кодобаза на новых плюсах будет в среднем в разы лаконичнее и надежнее её старого аналога.
То, что нет никаких гарантий распечатывания всех ошибок памяти.
скорее гарантируют чем нет. По крайней мере я не знаю сценарий, способный обмануть санитайзер.
Я уж не говорю о логистических проблемах сборки всех зависимостей с санитайзерами, о повышенном потреблении ресурсов
я говорю про юнит-тесты. В них запуск под санитайзерами оправдан в подавляющем большинстве случаев.
... о том, что некоторые ошибки проявляются только сильно спустя время, и так далее.
и в условном шарпе таких ошибок, не сразу проявляющихся, конечно же не бывает?
Почему неприменим-то?
Если хотите поспорить на тему "почему языки с GC не применимы в latency-critical системах" можете перейти в эту статью
0xd34df00d
08.11.2021 00:43+2расскажите подробнее. Сколько
С++17 — активно с появления
-std=c++1z
в компиляторах (2016, наверное?) по 2020-й год. Пара проектов — до сих пор, но в статусе поддержки жизнеобеспечения и устранении bitrot'а.C++20 — активно с появления
-std=c++2a
(2018? 2019? с концептами я начал играться ещё c concepts TS в gcc году в 2017-м) по 2020-й год. Аналогично про жизнеобеспечение и bitrot.Вообще мне очень смешно, когда мне начинают рассказывать, что все мои проблемы оттого, что я писал на якобы каких-то старых плюсах, учитывая, что я последние лет 10 на каждом своём месте работы (и в личных забавах тоже) очень активно проталкивал использование новых стандартов, фич и подходов. Неужели мои аргументы так похожи на стандартное олдфаговское нытьё из C++03 или 11?
объем проекта
От 50 до 800 kLOC.
какого размера команда
От одного человека (личные пет-проекты) до примерно десятка.
насколько там современный код?
ХЗ, в чём современность измерять, но практически каждая фишка из современных плюсов есть, включая всякие мелочи вроде инициализаторов в
if
,switch
иfor
или inline-переменных (TFW такая мелочь спасла идею в одной библиотеке). Не пришлось потыкать только модули. Ну и с корутинами я игрался не в боевом коде и даже не в пет-проектах-долгостроях, а в, как бы это сказать, однофайловом проекте.начиная с с++11 появляется всё больше инструментов, позволяющих решать задачи быстрее и надежнее. Кодобаза на новых плюсах будет в среднем в разы лаконичнее и надежнее её старого аналога.
Например? Ну, да, появился
operator<=>
, но подсчёт хеша, дебаговый вывод, сериализацию в жсон, беготню по деревьям, вложенныеoptional
и прочее всё равно приходится писать руками. Сравните сderiving (Show, Generic, Hashable, ToJSON, FromJSON)
(последние три делаются библиотекой на базеGeneric
-представления).Шаблонное метапрограммирование — да, стало приятнее, но это изначально был адово костыльный инструмент, в других языках построенный сильно более цельно. Плюс, в большинстве случаев вся эта камасутра не то что не нужна, но и даже скорее не приветствуется.
Antervis
08.11.2021 02:47Неужели мои аргументы так похожи на стандартное олдфаговское нытьё из C++98 или 03?
да, слово в слово. Ну или на нытьё студентов, которые хотят учиться программировать по питону вместо с++...
От 50 до 800 kLOC.
странно ловить описываемые вами проблемы (а их вы с учетом других дискуссий перечислили немало) в проектах таких масштабах с совеременными плюсами...
ХЗ, в чём современность измерять, но практически каждая фишка из современных плюсов есть
"Современность" кода это же не ачивка в стиме, важно не сколько единичных фич заиспользовано, а насколько хорошо ими покрыт код. Например "какой процент аллокаций через new/delete", "как часто аргументы передаются по указателю" и всё в таком духе.
Например?
мув семантика по сути дала адекватный способ передавать владение объектами не по сырому указателю (auto_ptr не в счет т.к. он сравнительно редко использовался и с ним свои нюансы). Ввели shared_ptr/unique_ptr, они как раз покрывают критикуемые вами кейсы. Асинхронное программирование сильно упростилось за счет лямбд, thread, mutex, и lock'ов.
Ну, да, появился
operator<=>
, но подсчёт хеша, дебаговый вывод, сериализацию в жсон, беготню по деревьям, вложенныеoptional
и прочее всё равно приходится писать руками. Сравните сderiving (Show, Generic, Hashable, ToJSON, FromJSON)
Я согласен, что чем короче/проще код, тем сложнее спрятать в нём ошибку. Собственно, на этом моя аргументация и сторится - код на с++ действительно стал короче и проще. А ваша позиция - "по сути ничего не поменялось". Так тогда возникает закономерный вопрос - ваше мнение изменится если скажем в с++ появится рефлексия?
0xd34df00d
11.11.2021 09:29+3да, слово в слово. Ну или на нытьё студентов, которые хотят учиться программировать по питону вместо с++...
У меня была немного другая картина того, на что ругаются олдфаги. Заметьте, я всегда, во всех этих тредах в первую очередь ругаюсь на фрактальную непознаваемость языка, на уймы UB, и так далее, а не на то, что, условно,
find_if
неюзабельный, потому что там надо создавать отдельный объект (причём вне функции, function-local class не может быть аргументом шаблона до C++11, как мы все, конечно, помним) или обмазываться динозаврами изstd::bind1st
какими-нибудь. От ваших олдфагов и прочих студентов я слышал в основном жалобы именно второго рода.странно ловить описываемые вами проблемы (а их вы с учетом других дискуссий перечислили немало) в проектах таких масштабах с совеременными плюсами...
Проблемы на самом деле возникают очень быстро. Как только вы хотите удостовериться, что вы пишете корректный с точки зрения стандарта код, у вас будут проблемы уже на 100 строках.
Собсна, чего далеко ходить — надо было написать свой циркулярный буфер, так там кода как раз на сто строк, но сидел и писал я его дня два, потому что большую часть этого времени бегал по стандарту и проверял, что и как я имею право делать с точки зрения лайфтаймов объектов для разных категорий объектов.
"Современность" кода это же не ачивка в стиме, важно не сколько единичных фич заиспользовано, а насколько хорошо ими покрыт код. Например "какой процент аллокаций через new/delete", "как часто аргументы передаются по указателю" и всё в таком духе.
Ну, один проект (личный), увы, на кутях со всеми вытекающими с наследованием от QObject и управлением памятью через голый new и иерархии, но в другом, например, аллокаций, считайте, не было вообще (потому что это была всякая торговая ерунда).
Плюс,
никтоя в здравом уме не буду начинать новые проекты на плюсах, а рефакторинг старых происходит не то чтобы сразу.мув семантика по сути дала адекватный способ передавать владение объектами не по сырому указателю (auto_ptr не в счет т.к. он сравнительно редко использовался и с ним свои нюансы). Ввели shared_ptr/unique_ptr, они как раз покрывают критикуемые вами кейсы. Асинхронное программирование сильно упростилось за счет лямбд, thread, mutex, и lock'ов.
Я прочитал ваш вопрос задницей, извините. А именно, почему-то я там увидел «после C++11», а не «начиная с C++11».
Обратите внимание, что большая часть упомянутых вами вещей появилась в C++11, что выглядит странно рядом с отсылками к тому, что у меня плюсы не семнадцатые. Ну и если по пунктам, то
Ввели shared_ptr/unique_ptr, они как раз покрывают критикуемые вами кейсы.
Все кодовые базы, с которыми я работал до 11, спокойно юзали
boost::shared_ptr
и не парились. Да, за счёт отсутствия мувов вещей вроде уник_птра не было, ну и хрен с ним, выражать семантику передачи владения без голых указателей уже можно было.Впрочем, они не покрывают критикуемые мной кейсы. Circular dependencies там бывают, кодовую базу на одних сплошь shared_ptr'ах, которые при этом умудрялись протухать, потому что там ещё были и weak_ptr'ы для разруливания этих зависимостей, меня тоже приглашали чинить (и это, наверное, единственный случай в моей жизни, когда я не справился за разумное время с починкой бага, и за полгода дебага и устранения проблем, которые, тем не менее, не кончались, мы решили просто всё нахрен переписать).
мув семантика по сути дала адекватный способ передавать владение объектами не по сырому указателю
При этом это добавляет интересных вещей в язык. Например, объяснять, почему плохо делать
T getFoo() { T foo; ...; return std::move(foo); }
мне приходилось.Асинхронное программирование сильно упростилось за счет лямбд, thread, mutex, и lock'ов.
Треды, мьютексы и локи де-факто были всегда. Лямбды — да, лямбды всё сильно упрощают.
Но при этом в нормальных языках есть, например, STM, который по сравнению с ручным управлением тредами как небо и земля.
Я согласен, что чем короче/проще код, тем сложнее спрятать в нём ошибку. Собственно, на этом моя аргументация и сторится — код на с++ действительно стал короче и проще. А ваша позиция — "по сути ничего не поменялось".
Да потому что принципиально ничего не поменялось по большому счёту, особенно после C++11. Ну да, лямбды всё сильно улучшили, но а что кроме этого? Range-based for? Так
BOOST_FOREACH
/Q_FOREACH
тоже были всегда (и, кстати, не забывайте не модифицировать контейнер, по которому бегаете). Spaceship? Опять недоделка — вместо рефлексии для описания произвольных функций выкатили это (и, кстати, тоже с весёлыми подводными камнями). Folding expressions, constexpr, етц? По моим грубым прикидкам большинство программистов на C++ этим всем не пользуется, и вполне оправданно.Так тогда возникает закономерный вопрос — ваше мнение изменится если скажем в с++ появится рефлексия?
Я боюсь, что рефлексии будет недостаточно по куче разных причин, но это совсем другая история.
Antervis
11.11.2021 10:40Заметьте, я всегда, во всех этих тредах в первую очередь ругаюсь на фрактальную непознаваемость языка, на уймы UB, и так далее
на UB ругаются все. Просто одни ругаются на то, что у них нет рантайм проверок на каждом шагу, а другие бесятся что компилятор делает какие-то там предположения когда они весьма вальяжно работают с памятью.
а не на то, что, условно,
find_if
неюзабельный, потому что там надо создавать отдельный объект (причём вне функции, function-local class не может быть аргументом шаблона до C++11, как мы все, конечно, помним) или обмазываться динозаврами изstd::bind1st
какими-нибудьну конкретно с find_if вроде либо использовали функцию, а-ля
find_if(.., isspace)
, либо писали циклом.Ну, один проект (личный), увы, на кутях со всеми вытекающими с наследованием от QObject и управлением памятью через голый new и иерархии
ну так new только для QObject иерархии и нужен, с передачей владения, т.е. просто так удалить не забудешь...
Все кодовые базы, с которыми я работал до 11, спокойно юзали
boost::shared_ptr
и не парилисьто у нас торговый бот, которому лишняя микросекунда вечность, то пофиг на рефкаунтинг... Хех.
При этом это добавляет интересных вещей в язык. Например, объяснять, почему плохо делать
T getFoo() { T foo; ...; return std::move(foo); }
мне приходилось.блин вы сравните последствия move вместо copy elision с одной стороны и выбором между копированием и выделением в куче с передачей по указателю с другой...
Треды, мьютексы и локи де-факто были всегда
не всегда переносимые
Folding expressions, constexpr, етц? По моим грубым прикидкам большинство программистов на C++ этим всем не пользуется, и вполне оправданно.
ну как, редко, но доводится, и как раз эти штуки сокращают код весьма мощно, а зачастую еще и время компиляции.
Spaceship? Опять недоделка — вместо рефлексии для описания произвольных функций выкатили это (и, кстати, тоже с весёлыми подводными камнями)
не "вместо" ведь... Просто пока рефлексия не готова, а потребность существует. А этот подводный камень не имеет отношения к spaceship, там разница именно в симметричности операторов в с++20+. Что кстати тоже сокращает код. Могли бы мб сделать явный квалификатор для этого, но их уже и так много...
Я боюсь, что рефлексии будет недостаточно по куче разных причин
ну для задач типа "сериализуй эту структуру с политикой по умолчанию" более чем хватит.
0xd34df00d
15.11.2021 01:05+1про сахар
На днях я написал такую функцию:
iipRange :: DispatchContext -> RangeWithId -> Neovim AgdaEnv (Maybe (Cursor64, Cursor64)) iipRange ctx range = withPayload ctx $ \payload -> do goalmarksId <- asks goalmarksNs >>= readTVarIO marks <- nvim_buf_get_extmarks (agdaBuffer ctx) goalmarksId (ObjectInt 0) (ObjectInt (-1)) [("details", ObjectBool True)] let res = do [markId] <- HM.lookup (id'range range) (interactionPoint2markIds payload) ObjectArray [ _, ObjectInt markRow, ObjectInt markCol, ObjectMap extras ] <- V.find (findById markId) marks ObjectInt endRow <- ObjectString "end_row" `M.lookup` extras ObjectInt endCol <- ObjectString "end_col" `M.lookup` extras pure (Cursor markRow markCol, Cursor endRow endCol) case res of Nothing -> log "extmark not found" $> Nothing Just r -> pure $ Just r where findById markId (ObjectArray ((ObjectInt markId') : _)) = markId == markId' findById _ _ = False
Это кусок плагина для поддержки одного ЯП для neovim'а, который по ID некоторой сущности в этом ЯП возвращает диапазон в виде пары курсоров (aka пары пар строка/столбец в редакторе), в котором эта сущность определена (если таковой есть).
Начинаем мы с того, что берём из
ctx
функциюwithPayload
, которая из контекстаctx
достаёт некоторое нужное нам поле. Внутри этой функции мы запрашиваем из окружения, в котором работает плагин, полеgoalmarksNs
, которое является мутабельной STM-переменной, хранящей инт, и считываем его текущее значение (кстати, на x86 компилятор автоматически убирает оттуда любые блокировки и просто считывает значение инта):goalmarksId <- asks goalmarksNs >>= readTVarIO
Потом мы у неовима запрашиваем все текущие extmark'и:
marks <- nvim_buf_get_extmarks (agdaBuffer ctx) goalmarksId (ObjectInt 0) (ObjectInt (-1)) [("details", ObjectBool True)]
Что такое extmark — не очень важно, но главное, что это возвращается neovim'ом в виде этакого
std::variant<ObjectInt, ObjectBool, ..., ObjectArray>
, если говорить на плюсовом жаргоне.Потом начинается веселье с optional'ами, которого в плюсах не будет просто никогда.
let res = do
— это значит, что дальше мы начинаем вычисления в монадеMaybe
, и их результат кладём вres
. В этой монаде мы ищем ключid'range range
в какой-то хешмапе, значения в которой — списки:[markId] <- HM.lookup (id'range range) (interactionPoint2markIds payload)
Если его нет,
HM.lookup
возвращаетNothing
, и всё выполнение прекращается сразу, возвращаяNothing
. Если он есть, то мы делаем паттерн-матчинг на этом списке. Если в нём один элемент, то он распаковывается вmarkId
. Если в нём нет элементов или больше одного элемента, то паттерн-матчинг неуспешен, и компилятор это дешугарит в вызов методаfail
тайпклассаMonadFail
.fail
дляMaybe
определён тупо как возвратNothing
, поэтому, да, вы угадали, если он неуспешен, то результатом всего выражения снова станетNothing
, и выполнение прервётся.Дальше мы ищем среди всех extmark'ов, полученных вначале, extmark с нужным нам ID, и аналогично паттерн-матчимся на его структуру:
ObjectArray [ _, ObjectInt markRow, ObjectInt markCol, ObjectMap extras ] <- V.find (findById markId) marks
— это должен быть массив (то есть, в
variant
е из аналогии выше должен лежатьObjectArray
) из четырёх элементов, второй и третий элемент должны быть интами, четвёртый элемент — мапой. Мы их сразу и выковыриваем при матчинге. Следующими двумя строками мы из мапы выковыриваем значения по соответствующим ключам и, наконец, всё возвращаем.Примерный аналог на плюсах выглядел бы так (пишу из головы с cppreference, компилябельность не проверяю):
// блин, как там с std::variant делаются рекурсивные варианты? // в любом случае, следующие строки кода в библиотеке, но они нужны для понимания боли: struct ObjectInt { int theInt; }; struct ObjectString { std::string theString; }; struct ObjectMap; struct ObjectArray; using NeovimObject = std::variant<ObjectInt, ObjectString, std::shared_ptr<ObjectMap>, std::shared_ptr<ObjectArray>...>; struct ObjectMap { std::map<NeovimObject, NeovimObject> map; }; struct ObjectArray { std::vector<NeovimObject> objs; }; struct Nothing {}; // дальше ваш код // я даю фору и выношу некоторую вспомогательную функцию: template<typename Ex, typename F> auto hoistNothing(F&& f) { try { return std::forward<F>(f)(); } catch (const Ex&) { throw Nothing {}; } } std::optional<std::tuple<Cursor, Cursor>> iipRange(const DispatchContext& ctx, const Range& rng, const AgdaEnv& env) { return ctx.withPayload([&](const auto& payload) { const auto goalmarksId = env.goalmarksNs.load(std::memory_order_relaxed); const auto marks = nvim_buf_get_extmarks(ctx.agdaBuffer, goalmarksId, ObjectInt { 0 }, ObjectInt { -1 }, std::map { ObjectString { "details" }, ObjectBool { true } }); try { // (1) auto markIds = hoistNothing<std::out_of_range>([&] { return payload.interactionPoint2markIds.at(range.idRange); }); // (2) if (markIds.size() != 1) { // (3a) throw Nothing {}; } const auto markId = markIds[0]; // (3a) const auto markPos = std::find_if(marks.begin(), marks.end(), [&markId] (const auto& mark) { if (const auto ptr = std::get_if<std::shared_ptr<ObjectArray>>(mark)) { // (4) const auto& objs = (**ptr).objs; if (objs.empty()) { return false; } if (const auto firstPtr = std::get_if<ObjectInt>(objs[0])) { // (5) return firstPtr->theInt == markId; } return false; } return false; }); if (markPos == marks.end()) { // (6) throw Nothing {}; } const auto& mark = *markPos; // (6) const auto markArray = hoistNothing<std::bad_variant_access>([&] { return std::get<std::shared_ptr<ObjectArray>>(mark); })->objs; if (markArray.size() != 4) { // (3b) throw Nothing {}; } const auto [markRow, markCol, extras] = hoistNothing<std::bad_variant_access>([&] { // (7) return std::make_tuple( std::get<ObjectInt>(markRow[1]).theInt, // (3b) std::get<ObjectInt>(markRow[2]).theInt, // (3b) std::get<std::shared_ptr<ObjectMap>>(markRow[3])->map); // (3b) }); const auto endRow = hoistNothing<std::out_of_range>([&] { const auto& var = hoistNothing<std::bad_variant_access>([&] { return extras.at(ObjectString { "end_row" }); }); return std::get<ObjectInt>(var).theInt; }); const auto endCol = hoistNothing<std::out_of_range>([&] { const auto& var = hoistNothing<std::bad_variant_access>([&] { return extras.at(ObjectString { "end_col" }); }); return std::get<ObjectInt>(var).theInt; }); return std::make_tuple(Cursor { markRow, markCol }, Cursor { endRow, endCol }); } catch (const Nothing&) { log("extmark not found"); return std::nullopt; } )); }
Комментарии:
- Ой, экзепшоны для управления control flow. Щито поделать десу.
- Обратите внимание, что замена
at
наoperator[]
не сломает компилябельность этого кода, но поменяет семантику. Если завтра ваш код придёт мейнтейнить мид, который где-то прочитал, чтоoperator[]
быстрее/лучше/етц, и заменит его здесь, код просто тихо перестанет кидать экзепшон. Потому что список экзепшонов не видно в типах, аMaybe
— видно. - Обратите внимание, что проверка на длину массива и доступ к элементу разнесены синтаксически, и проверку можно случайно забыть, от чего компилябельность программы не пострадает. Можно было бы совместить через
vector::at
вместоoperator[]
, завернув заодно вhoistNothing
, но довольно забавно, что это будет медленнее паттерн-матчинга в хачкеле. - Ой, сырые указатели. So much for modern C++.
- Ой, сырые указатели. So much for modern C++.
- Снова обратите внимание, что проверка и доступ синтаксически разнесены. Снова проверку можно забыть. Этого вообще, принципиально не может произойти с паттерн-матчингом.
- Ура, тут мы стали C++20-only, потому что до C++20 нельзя капчурить structured bindings в лямбды.
Кроме того, код на хаскеле я писал, не особо думая, как только понял структуру ответов от neovim'а. Код на плюсах мне пришлось писать минут 40 и подключая мозг и cppreference.
В качестве задачи со звёздочкой предлагаю прикинуть, что нужно будет сделать, чтобы вместо
std::optional<T>
возвращать аналогEither String T
с ошибкой, которая плюс-минус поможет вам как автору потом понять, что именно сломалось. Мне в хаскель-версии потребуется по большому счёту только добавить вызовmaybeToRight
(который, кстати, можно найти по типам) в двух местах (заменив, например,V.find (findById markId) marks
наmaybeToRight "finding in
marksfailed" $ V.find (findById markId) marks
) и написав рукамиinstance MonadFail (Either String) where fail = Left
потому что академики по не очень понятным мне причинам не хотят добавлять этот или подобные инстансы в базовые библиотеки.
Статью написать, что ли.
Antervis
15.11.2021 06:56Первое есть подмножество второго, к слову.
те, кто гуляет по UB и бесится что компилятор не дает им работать с памятью как с простым массивом байт точно не являются подмножеством тех, кто хочет рантайм проверку повсюду.
Ну так то было 10-15 лет назад, я тогда ботами ещё не занимался. А в тех ботах, которыми я занимался недавно, вы и
unique_ptr
не найдёте.trivial ABI для unique_ptr пробовали?
Просто как медицинский факт — до C++11 люди как-то более-менее код писали относительно успешно
как только мне доводится залазить в с++03 код, обычно одних лишь подсказок IDE достаточно чтобы насчитать с десяток лишних копий в одном файле. Просто критерий "успешности" тоже эволюционировал.
Можете назвать платформу, для которой сейчас нет бустовских/ACE/POCO/етц-тредов и мьютексов, а вот стандартные плюсовые там есть?
ну на винде нет толкового posix, а всё остальное не всегда хочется тащить в проект.
Сколько времени вам (и прочим, всё ещё читающим тред) понадобится, чтобы распарсить, что здесь происходит? Чур не запускать.
мне понадобилась пара секунд, вероятно потому, что я ожидал такой выпад. Лучше было бы написать так - корректнее и читаемее:
template <typename F, typename ...Args> void foo(F&& f, Args&&... args) { (f(std::forward<Args>(args)), ...); }
Или хитрая засада была в вычислении задом наперед?
На днях я написал такую функцию:
там весь пример как назло переусложнен. Всякие ObjectInt/ObjectMap и т.д. никто в здравом уме делать не будет, используются using или в худшем случае, если нужен distinct type, делают через наследование. Вместо
std::make_tuple
тут нуженstd::tie
. Но это всё мелочи.Важно то, что вы смешали optional и исключения. Если писать только с исключениями, вообще без optional, код можно очень сильно сократить, а бонусом будет прокидываться текст ошибки из "задачи со звёздочкой".
И сдается мне что если писать только на optional, без исключений, то код тоже может получиться короче, точнее, длиннее, но менее объемным. Грубо говоря,
struct Nothing
не нужен, все её использования спокойно заменяются на returnstd::nullopt
, а реальное экономие на исключениях у вас всего несколько строк внутри лямбд, что вы в общем-то скомпенсировали hoist'ом.Конечно же код с optional был бы еще короче будь в с++ монадические интерфейсы, их не хватает, и с этим я согласен.
Ура, тут мы стали C++20-only, потому что до C++20 нельзя капчурить structured bindings в лямбды.
можно, через [&foo=foo]. Согласен, хак.
0xd34df00d
15.11.2021 21:06+1те, кто гуляет по UB и бесится что компилятор не дает им работать с памятью как с простым массивом байт точно не являются подмножеством тех, кто хочет рантайм проверку повсюду.
Мы начали вообще с того, кто там какие гарантии где даёт. И если уж гулять, то хотелось бы, чтобы места, в которых код может гулять по памяти, были как-то явно выделены и изолированы. Ну там, не знаю,
unsafe
ом каким-нибудь, например.trivial ABI для unique_ptr пробовали?
Оно уже стало стандартным?
ну на винде нет толкового posix, а всё остальное не всегда хочется тащить в проект.
Ну так и даже C++11 даже сегодня не всем хочется тащить в проект. Что там кому хочется тащить, ИМХО, вообще не аргумент, особенно в случае с такими устоявшимися библиотеками, как boost.
мне понадобилась пара секунд, вероятно потому, что я ожидал такой выпад. Лучше было бы написать так — корректнее и читаемее:
Неа, вы потеряли семантику.
Или хитрая засада была в вычислении задом наперед?
Именно.
там весь пример как назло переусложнен. Всякие ObjectInt/ObjectMap и т.д. никто в здравом уме делать не будет, используются using или в худшем случае, если нужен distinct type, делают через наследование.
Подождите. Как мне через плюсовый
std::variant
выразить рекурсивную структуру данных? Ну, то есть, как бы вы тогда писали аналогdata Object = ObjectInt Int | ObjectString String | ObjectArray [Object] | ObjectMap (Map Object Object)
Так вы написать по очевидным причинам не можете:
using Object = std::variant<ObjectInt, ObjectString, std::vector<Object>, std::map<Object, Object>>;
Важно то, что вы смешали optional и исключения. Если писать только с исключениями, вообще без optional, код можно очень сильно сократить, а бонусом будет прокидываться текст ошибки из "задачи со звёздочкой".
Во-первых, использование исключений сократит только внешний
try/catch
, не более.Во-вторых, вызывающий код ничего не хочет знать про исключения, вызывающий код хочет получать
std::optional<T>
и проверять её. А с исключениями у вас есть три варианта:- Ловить в вызывающем коде любое исключение и интерпретировать его как
Nothing
. Ну так себе идея, ИМХО. - Ловить в вызывающем коде только
std::out_of_range
иstd::bad_variant_access
— которые совершенно не видно в типе функции, которые являются деталями реализации, но при этом протекают в её контракт. Это ад с точки зрения поддержки и композабельности функций. - Ловить в вызывающем коде какой-то один конкретный вид исключений (тот же
Nothing
), а в этой функции преобразовывать типыhoist
ом. Но тогда вы сэкономили в прямом смысле три строки (да и то на самом деле нет, потому что логгинг в конце вам таки нужен).
Грубо говоря, struct Nothing не нужен, все её использования спокойно заменяются на return std::nullopt
Нет, не заменяется, потому что ну попробуйте тогда переписать код, который перепрокидывает
out_of_range
илиbad_variant_access
, на код, который при этом возвращаетnullopt
. Вам придётся либо костылять в духеconst auto maybeRes = hoist<std::out_of_range>(...); if (!maybeRes) { return std::nullopt; } const auto& res = *maybeRes;
вместо
const auto& res = hoist<std::out_of_range>(...);
либо даже не знаю, что делать.
Конечно же код с optional был бы еще короче будь в с++ монадические интерфейсы, их не хватает, и с этим я согласен.
Так тут прелесть не только в том, что есть сахар для монад, но и в том, что, например, есть паттерн-матчинг, и этот паттерн-матчинг автоматически работает с монадическим сахаром для монад, которые
MonadFail
.- Ловить в вызывающем коде любое исключение и интерпретировать его как
Antervis
15.11.2021 23:15И если уж гулять, то хотелось бы, чтобы места, в которых код может гулять по памяти, были как-то явно выделены и изолированы
это то тут при чем? Вот я утверждаю: есть люди, которые чаще всего нынешние или бывшие сишники, которые чаще пишут под всякую микруху, и которые критикуют с++ за оптимизации на основе UB, которые видите ли ломают им код. Кажется даже здесь где-то были.
Неа, вы потеряли семантику. ... Именно.
ну то есть по итогу не потерял. Правда, не понимаю, чего я добился или чего вы хотели сказать. В каждом языке есть свои хитрые нечитаемые трюки, в с++ их наверно даже меньше чем в условном js.
Подождите. Как мне через плюсовый
std::variant
выразить рекурсивную структуру данных?как ObjectInt/ObjectString помогают вам выразить рекурсивную структуру данных? Никак же, просто бойлерплейт.
Касательно всего следующего - здесь я причесал ваш сниппет согласно моим же замечаниям, плюс пара мелочей - забытые &, подчеркнул потенциальный null deref, кое где привел к стилю.
Детальнее тут
Давайте начнем с того, что в плюсах конструкция
return ctx.withPayload([&ctx](auto& payload){...})
выглядит странно - в объект передается лямбда, захватывающая и использующая этот объект. Обычно бы написали функцию, принимающую ctx аргументом вместе с payload'ом. И вся портянка была бы в этой функции, как-то так.Во-первых, использование исключений сократит только внешний
try/catch
, не более.в качестве абсолютного минимума оно еще убирает hoistNothing, а приведение исключений к результату выносится во внешний catch блок.
Во-вторых, вызывающий код ничего не хочет знать про исключения, вызывающий код хочет получать
std::optional<T>
и проверять еёА вызывающий код так хочет потому что... ээм... вы взяли пример из реализации на хаскеле, языке, в котором принято возвращать значение типов-сумм/произведений? В с++ то таких ограничений нет. И совершенно нормально если с++ API кидает исключения. Разве что еще приветствуется в дополнение к кидающим версиям делать не кидающие.
А с исключениями у вас есть три варианта:
(три убогих варианта)
4. ловить
std::exception&
и брать текст ошибки изe.what()
?Да, хаскель версия всё еще короче. Но не настолько короче и не настолько читаемее (если), чтобы говорить о какой-то кардинальной разнице.
0xd34df00d
17.11.2021 22:34+2это то тут при чем?
При лично моих фантазиях и мечтах. Но это уже совсем оффтопик, согласен.
ну то есть по итогу не потерял. Правда, не понимаю, чего я добился или чего вы хотели сказать.
Как это не потерял? Ваш вариант применяет функцию к аргументам в прямом порядке, мой — в обратном. Цикл с начала в конец или с конца в начало для вас эквивалентны, что ли?
В каждом языке есть свои хитрые нечитаемые трюки, в с++ их наверно даже меньше чем в условном js.
Зависит от вашего опыта с шаблонным метапрограммированием, тащем-та. Для значимой части людей всё это — нечитаемые трюки.
как ObjectInt/ObjectString помогают вам выразить рекурсивную структуру данных? Никак же, просто бойлерплейт.
Помогают не думать о том, есть ли у какого-то другого поля в variant'е неявное преобразование из инта, и как
variant
с этим будет работать, особенно если я завтра поменяюint
наuint64_t
— плюсы не настолько дружественны к механическому рефакторингу, чтобы не думать об этом наперёд. Тем более, что с точки зрения производительности (раз мы уж тут о плюсах говорим) вполне разумно ожидать, что такие обёртки не дадут никакого оверхеда по сравнению с голым интом.Олсо, в хаскель-версии есть ObjectString и ObjectBlob (не используемый в этом примере, правда), так что разные типы нужны.
Касательно всего следующего — здесь я причесал ваш сниппет согласно моим же замечаниям
// throw Nothing{}; return {};
Потеряли логгинг о ненайденном экстмарке в этом и двух следующих случаях.
const auto ptr = std::get_if<std::shared_ptr<ObjectArray>>(mark); if (!ptr || !*ptr || ptr->objs.empty()) { // note *ptr checked against nullptr return false; } const auto firstPtr = std::get_if<ObjectInt>(objs[0]); return firstPtr && (*firstPtr == markId);
Круто. А теперь сравните с
findById markId (ObjectArray ((ObjectInt markId') : _)) = markId == markId' findById _ _ = False
В каком случае вам нужно больше напрягаться, чтобы понять, что происходит?
catch (const std::exception& e)
Ловите все исключения, включая те, которые должны пробрасываться наверх. Недружественно к рефакторингу на будущее.
подчеркнул потенциальный null deref
О, а это интересно. По логике там никогда не должно оказаться пустого указателя, но на плюсах я не могу это выразить, поэтому приходится добавлять проверки (кто там что говорил про производительность)?
в качестве абсолютного минимума оно еще убирает hoistNothing, а приведение исключений к результату выносится во внешний catch блок.
И в итоге вы ловите любое исключение. Ну, про это я написал выше.
А вызывающий код так хочет потому что… ээм… вы взяли пример из реализации на хаскеле, языке, в котором принято возвращать значение типов-сумм/произведений? В с++ то таких ограничений нет. И совершенно нормально если с++ API кидает исключения.
В хаскеле таких ограничений тоже нет, там вы тоже можете кидать исключения, это не идрис и не агда. Просто исключения не видно в типах, из-за чего
- Вы не уверены при рефакторинге, что вы ничего не сломали.
- Вы не уверены, что вы обрабатываете те и только те ошибки, которые хотите обработать.
- Компилятор вам больше не друг и не помощник в этом всём.
Поэтому их в любом языке лучше избегать.
Antervis
18.11.2021 03:40Как это не потерял? Ваш вариант применяет функцию к аргументам в прямом порядке, мой — в обратном
я не то имел в виду
Зависит от вашего опыта с шаблонным метапрограммированием, тащем-та. Для значимой части людей всё это — нечитаемые трюки.
для людей, которые не освоились с с++11 то? Ну хз, хз. Да, я знаю что fold ввели в с++17, но его можно эмулировать в с++11
Помогают не думать о том, есть ли у какого-то другого поля в variant'е неявное преобразование из инта, и как
variant
с этим будет работать, особенно если я завтра поменяюint
наuint64_t
— плюсы не настолько дружественны к механическому рефакторингу, чтобы не думать об этом наперёдэто можно делать либо type alias'ом, либо, на совсем худой конец, через
enum class foo : int;
- создает distinct type. Точно так же как classObjectString : public std::string;
. И без бойлерплейтаТем более, что с точки зрения производительности (раз мы уж тут о плюсах говорим) вполне разумно ожидать, что такие обёртки не дадут никакого оверхеда по сравнению с голым интом.
в теории могут - это ведь описывает даже не компилятор/стандарт, а x64 ABI.
Потеряли логгинг о ненайденном экстмарке в этом и двух следующих случаях.
Кидать исключение ради лога странно, лучше вставить логгирование перед
return {}
.В каком случае вам нужно больше напрягаться, чтобы понять, что происходит?
в хаскеле, потому что я его не знаю, и потому что в нём логика задом-наперед.
Ловите все исключения, включая те, которые должны пробрасываться наверх. Недружественно к рефакторингу на будущее.
во-первых, вы выше заявили что функция должна возвращать optional и не кидать исключения. Во-вторых, я повторил поведение оригинала (ну, кроме логгинга). В-третьих, ну будет там тогда два разных catch блока с
std::bad_variant_access
иstd::out_of_range
вместо одного сstd::exception
, неужели это так принципиально?О, а это интересно. По логике там никогда не должно оказаться пустого указателя, но на плюсах я не могу это выразить, поэтому приходится добавлять проверки (кто там что говорил про производительность)?
блин вы могли сказать "вот видите, я же допустил ошибку", на что я бы ответил "а вдруг специально?", но нет же, надо в дебри... Вы очень даже можете это выразить, просто не через
shared_ptr
, а сделав другой класс с необходимой семантикой. Единственное destructive move в с++ всё еще нет, поэтому сделать non-null unique_ptr будет проблемно.В хаскеле таких ограничений тоже нет, там вы тоже можете кидать исключения, это не идрис и не агда. Просто исключения не видно в типах
где конкретно я вижу тип исключения в операции
5 / 0
? Как это поможет по пунктам 1, 2, 3?Поэтому их в любом языке лучше избегать.
удачи избегать их в доброй половине ЯП, ага
eao197
12.11.2021 11:10Собсна, чего далеко ходить — надо было написать свой циркулярный буфер, так там кода как раз на сто строк, но сидел и писал я его дня два, потому что большую часть этого времени бегал по стандарту и проверял, что и как я имею право делать с точки зрения лайфтаймов объектов для разных категорий объектов.
А попробуйте спроецировать эту задачу на другие языки программирования. Ну там C#/Java, Haskell, Rust. За счет чего бы вы получили упрощение своей работы и какую бы производительность/предсказуемость получили бы в итоге (равно как и функциональность)?
Уж не за счет ли наличия GC в C#/Java/Haskell (с boxing-ом)? И не за счет ли отсутствия стандарта как такового у Rust-а?
Antervis
12.11.2021 13:45И не за счет ли отсутствия стандарта как такового у Rust-а?
ну раст не умеет ни в исключения, ни в нетривиальный мув, поэтому логика контейнера очень сильно упрощается. А еще в расте считается, что всё что safe и скомпилилось не содержит UB. Правда, для реализации полноценного буфера понадобится unsafe, а там гарантий практически нет...
warlock13
12.11.2021 13:58раст не умеет ни в исключения, ни в нетривиальный мув, поэтому логика контейнера очень сильно упрощается
К сожалению или к счастью panic-safety всё же требуется от любой корректной библиотеки. А это почти то же самое, что exception-safety. (И хотя я не представляю чтобы вменяемый человек собирал программу с настройкой, отличной от `panic = abort`, у других разработчиков - разработчиков приложений - может быть иное мнение, которое мне как разработчику библиотеки приходится учитывать.)
А преимущество Rust в том, что для каждой unsafe функции в её документации есть раздел "Safety" и требования там простые и ясные. Есть, конечно,ещё ряд требований общего характера, среди которых самое сложное - это соблюдение "stacked borrowing", но в основном всё-таки при выяснении что в Rust UB, а что нет ты удивляешься, когда узнаёшь, что _что-то не является UB_, а в плюсах, к сожалению, наоборот.
Antervis
12.11.2021 15:07К сожалению или к счастью panic-safety всё же требуется от любой корректной библиотеки
а компилятор этого требует? В смысле "скомпилирует ли код, не удовлетворяющий panic safety"? Так или иначе, тривиальность мува сильно упрощает задачу (а точнее, отсутствие необходимости поддерживать исключение/панику при муве).
А преимущество Rust в том, что для каждой unsafe функции в её документации есть раздел "Safety" и требования там простые и ясные.
так речь же не об использовании unsafe функций, а о написании unsafe кода. Там с гарантиями всё намного хитрее, и стандарта, по которому можно было бы перепроверить код, попросту не существует.
но в основном всё-таки при выяснении что в Rust UB, а что нет ты удивляешься, когда узнаёшь, что что-то не является UB, а в плюсах, к сожалению, наоборот.
это всё круто, учитывая что состав UB rust зависит от llvm, поведение миддленда которого по большей части определяется стандартом плюсов...
warlock13
13.11.2021 20:11а компилятор этого требует?
Нет, не требует. И код, нарушающий panic safety, является sound. Да и нестрогое это понятие, неформальное, хотя и не вызывающее разногласий в реальных конкретных случаях.
так речь же не об использовании unsafe функций, а о написании unsafe кода
А он и сводится на 95% к использованию unsafe функций - таких как
std::ptr::write
,std::mem::MaybeUninit::assume_init
,std::mem::transmute
и т. д. Остальное - это всего несколько хорошо известных правил что можно делать, а что нет.состав UB rust зависит от llvm
Да нет, не зависит. Но если вы можете привести конкретный пример, мне было бы интересно.
Antervis
14.11.2021 04:44Нет, не требует. И код, нарушающий panic safety, является sound
то есть через нарушение panic safety можно допустить UB в safe rust?
Да нет, не зависит. Но если вы можете привести конкретный пример, мне было бы интересно.
Например отсюда есть пара ссылок на доку LLVM, namely "pointer aliasing rules" и "uninitialized memory".
А вообще если судить по списку потенциальных UB transmute, кажется основной принцип UB в раст таков, что правила сильно строже, для простоты. Взять например правило что каст & в &mut это UB, в отличие от плюсов, где UB - модификация const объекта, а кастить туда-обратно можно сколько влезет. Впрочем, раст запрещает "mutating immutable data" отдельно. Или например для структуры
struct S { int x, y; }
c++ позволит сделать каст S* -> int* -> S* без UB (т.к. первый член S - int), в то время как в rust второй каст будет UB, ведь "it is Undefined Behavior for U to be larger than T".
warlock13
15.11.2021 06:39it is Undefined Behavior for U to be larger than T
Где это вы такое нашли? Это неправда.
Aldrog
15.11.2021 12:49Беглый гугл подсказал, что это о копировании с помощью
mem::transmute_copy<T, U>
, не о касте.
Antervis
15.11.2021 13:34Где это вы такое нашли? Это неправда.
Здесь: "
mem::transmute_copy<T, U>
somehow manages to be even more wildly unsafe than this. It copiessize_of<U>
bytes out of an&T
and interprets them as aU
. The size check thatmem::transmute
has is gone (as it may be valid to copy out a prefix), though it is Undefined Behavior forU
to be larger thanT
".По умолчанию компилятор такое не пропустит, если вам от этого легче
Беглый гугл подсказал, что это о копировании с помощью
mem::transmute_copy<T, U>
, не о касте.Оттуда же: "Also of course you can get all of the functionality of these functions using raw pointer casts or
union
s, but without any of the lints or other basic sanity checks. Raw pointer casts andunion
s do not magically avoid the above rules."
warlock13
16.11.2021 07:11Ну всё-таки здесь нет однозначного запрета. И на самом деле в Rust кастить указатели можно любой к любому, а правила разименования того что получилось _менее строгие_ чем в C++.
Antervis
16.11.2021 10:16Ну всё-таки здесь нет однозначного запрета. И на самом деле в Rust кастить указатели можно любой к любому, а правила разименования того что получилось менее строгие чем в C++.
в rust некорректный каст указателя уже является UB, и это куда более строгий запрет, чем в с++. А потом разыменовывай сколько влезет, ага.
Aldrog
16.11.2021 13:28Ну камон, то что вы цитируете про размеры типов очевидно относится не к касту, а к операции «каст + копирование».
Raw pointer casts and unions do not magically avoid the above rules.
И вот эта фраза никак этого не отменяет.
Antervis
16.11.2021 14:02Ну камон, то что вы цитируете про размеры типов очевидно относится не к касту, а к операции «каст + копирование».
во-первых, transmute_copy это не "каст + копирование", а "копирование + каст", и весьма очевидно что UB кроется в касте, а не в копировании. Во-вторых, там буквально написано что А. transmute_copy'ровать в тип большего размера - UB, и B. это правило распространяется и на касты указателей.
И вот эта фраза никак этого не отменяет.
Она буквально и прямым текстом говорит что касты указателей никаким волшебным образом не обходят правила transmute/transmute_copy. И единственная причина почему все эти UB не перечислены в главе про указатели - она неполная (за незавершенностью модели памяти), сказано лишь что *T всегда должен указывать на valid instance of T, и что нужно придерживаться правил pointer aliasing'а и borrowing'а.
Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?
Aldrog
16.11.2021 14:50весьма очевидно что UB кроется в касте, а не в копировании
Не очень понимаю, почему для вас копирование
sizeof<U> > sizeof<T>
байт из переменной типа T очевидно не является UB, а каст является.Она буквально и прямым текстом говорит что касты указателей никаким волшебным образом не обходят правила transmute/transmute_copy.
Она говорит, что сделать то же самое, но избежав UB в обозначенных случаях, у вас не выйдет, только и всего.
Согласен, что раздел о кастах выглядит не совсем полным, но в списке UB кастов тоже нет, только разыменования.
Да и в многочисленных предупреждениях о незавершённости модели памяти тоже говорится о разыменованиях, а не о самих кастах.Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?
Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.
Antervis
16.11.2021 15:45Не очень понимаю, почему для вас копирование
sizeof<U> > sizeof<T>
байт из переменной типа T очевидно не является UB, а каст является.потому что я исхожу из предположения, что указатель смотрит на valid instance U или что-то layout-compatible с U. В противном случае UB ожидаемо гарантирован и при касте, и при копировании. Но ведь документация говорит не так, она говорит что transmute_copy в тип большего размера это UB независимо от всего остального.
Согласен, что раздел о кастах выглядит не совсем полным, но в списке UB кастов тоже нет, только разыменования.
а еще в списке UB написано что он неполный, и про касты/трансмьюты там ничего нет. Могли бы хоть добавить туда индекс со ссылками на все UB, обозначенные в других разделах.
Да и в многочисленных предупреждениях о незавершённости модели памяти тоже говорится о разыменованиях, а не о самих кастах.
пока модель памяти не завершена, поведение всего, что от неё зависит, не определено, т.е. UB. Ну это так, к слову.
Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.
значит вопрос в том, кто из нас смотрит в документацию и видит в ней то, что там написано, верно?
Aldrog
16.11.2021 19:24потому что я исхожу из предположения, что указатель смотрит на valid instance U или что-то layout-compatible с U. В противном случае UB ожидаемо гарантирован и при касте, и при копировании.
Она же вообще не указатель, а ссылку принимает. По сути функция идентична
std::bit_cast
из C++20, предназначена исключительно для type punning. И, кстати, UB у них очень похожи.Но ведь документация говорит не так, она говорит что transmute_copy в тип большего размера это UB независимо от всего остального.
Ограничения
mem::transmute
, я так понимаю, наmem::transmute_copy
тоже распространяются.а еще в списке UB написано что он неполный, и про касты/трансмьюты там ничего нет.
То что UB в библиотечных функциях описываются отдельно от UB в языке на мой взгляд вполне нормально.
А вот касты — фича языка, и описания UB в ней я бы ожидал видеть и в этом списке, и в документации кастов, но их нет ни там, ни там. Я из этого могу сделать вывод, что либо UB возникает только при разыменовании, либо в документации есть ошибки.
Совершенно не понимаю, как вы делаете вывод, что документация напрямую говорит о UB при определённых кастах.
Antervis
16.11.2021 21:52Она же вообще не указатель, а ссылку принимает. По сути функция идентична
std::bit_cast
из C++20, предназначена исключительно для type punning. И, кстати, UB у них очень похожина transmute похож, да. Но bit_cast не умеет в типы разных размеров, в отличие от transmute_copy.
А вот касты — фича языка, и описания UB в ней я бы ожидал видеть и в этом списке, и в документации кастов, но их нет ни там, ни там. Я из этого могу сделать вывод, что либо UB возникает только при разыменовании, либо в документации есть ошибки.
либо дока неполная и она еще не определяет то поведение, которое компилятор rust будет готов гарантировать. Ну а любое поведение которое еще не определено буквально является UB
Совершенно не понимаю, как вы делаете вывод, что документация напрямую говорит о UB при определённых кастах.
я говорю про конкретный UB, который прямым текстом прописан для конкретной операции и на конкретную сноску про то, как этот конкретный UB распространяется и на касты/union'ы.
Aldrog
16.11.2021 23:20на transmute похож, да.
transmute ещё и что-то хитрое с лайфтаймами делает, так что transmute_copy более похож.
Но bit_cast не умеет в типы разных размеров, в отличие от transmute_copy.
Это, насколько могу судить, единственное существенное отличие — возможность скопировать первые
sizeof<U>
байт при разных размерах. И именно этим обусловлено UB приsizeof<U> > sizeof<T>
.Ну а любое поведение которое еще не определено буквально является UB
Тогда надо смотреть сюда, в описание всех возможных типов кастов. Там и для pointer to pointer cast описаны ограничения
*V where V: Sized
т.е. размер целевого типа должен быть известен при компиляции
or T and V are compatible unsized types, e.g., both slices, both the same trait object.
И ниже очередная сноска на memory model:
Warning: This interacts with the Rust memory model, which is still under development. A pointer obtained from this cast may suffer additional restrictions even if it is bitwise equal to a valid pointer. Dereferencing such a pointer may be undefined behavior if aliasing rules are not followed.
И снова предупреждают только о разыменовании.
я говорю про конкретный UB, который прямым текстом прописан для конкретной операции
Операции, которая не ограничивается кастом, а в плюсах и вовсе реализовывалась бы без (явных) кастов
template<typename U, typename T> U transmute_copy(const T &t) { U u; std::memcpy(&u, &t, sizeof(U)); return u; }
и на конкретную сноску про то, как этот конкретный UB распространяется и на касты/union'ы.
Всё-таки не «распространяется на», а «не обходится ими».
Antervis
17.11.2021 00:05Это, насколько могу судить, единственное существенное отличие — возможность скопировать первые
sizeof<U>
байт при разных размерах. И именно этим обусловлено UB приsizeof<U> > sizeof<T>
.Речь идет о касте T в U, а не наоборот, т.е. когда копируется больше байт, чем есть в T
И снова предупреждают только о разыменовании.
потому что всё остальное under development? Сформулируйте пожалуйста требования к указателю в rust, чтобы его разыменование гарантированно не приводило к UB.
Всё-таки не «распространяется на», а «не обходится ими».
как ни фразеологируйте, итог то один - UB при преобразовании T в U большего размера будет и при использовании transmute_copy, и при касте через указатели и union'ы.
warlock13
17.11.2021 09:57Как справедливо заметили,
transmute_copy
принимает параметром не указатель, а ссылку, и это всё меняет.Документация функции `transmute_copy` описывает условия, при которых использование этой функции безопасно или нет. Она не должна описывать и не описывает общие UB на уровне языка.
Список UB уровня языка неполный, потому что он не покрывает то, что называется "stacked borrowing". Stacked borrowing ("модель памяти") пока не утверждено окончательно, но практика программирования на Rust учитывает это ограничение. Собственно, именно на нарушение stacked borrowing вы и наскочите при попытке обойти обсуждаемое ограничение `transmute_copy`.
Ещё в списке не упонимается pointer provenance - тоже формально не описанное (или недоописанное) UB, но хорошо известное и принимаемое во внимание теми, кто пишет unsafe код. В остальном, список полный.
Запрета на каст указателей в списке нет по очень простой причине: потому что нет такого запрета вообще (если иметь в виду простые, тонкие указатели на
Sized
типы; с толстыми ситуация несколько иная, но так же интуитивно-ясная).
Antervis
17.11.2021 11:22Как справедливо заметили,
transmute_copy
принимает параметром не указатель, а ссылку, и это всё меняет.приведение указателя к ссылке считается dereference'ом?
Ещё в списке не упонимается pointer provenance - тоже формально не описанное (или недоописанное) UB, но хорошо известное и принимаемое во внимание теми, кто пишет unsafe код. В остальном, список полный.
так pointer provenance и является причиной почему такой каст может приводить к UB...
0xd34df00d
15.11.2021 01:09+1В буфере, который был мне нужен, тоже не было ни исключений, ни нетривиальных мувов. Это был буфер, считайте, для обёрток над интами (у которых тоже бывают лайфтаймы, однако).
0xd34df00d
15.11.2021 01:07+2Нет, разница тут в том, что C++ — единственный язык (из известных мне, по крайней мере), в котором
- есть концепция лайфтаймов объектов, и
- лайфтаймы объектов не привязаны к лексическому скоупу имён, и
- компилятор имеет право пользоваться нарушением лайфтаймов для оптимизации, и
- компилятор имеет право молчать как партизан, когда он этим таки пользуется.
Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.
eao197
15.11.2021 07:08из известных мне, по крайней мере
Из тех, с чем дело имел я, это еще и чистый Си и Pascal. И, если мне не изменяет склероз, Modula-2 и Ada. На счет Fortran-а не в курсе, дел не имел и даже не интересовался.
Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.
А теперь перестаньте изливать свою боль про нагадивший вам в шаровары C++ и попробуйте ответить, почему же в языках с GC нет таких страшных проблем с контролем лайфтаймов. И какой ценой в Rust-е попробовали достичь того же без GC (и сколько времени ушло на то, чтобы реальная альтернатива старым языкам без GC появилась в лице того же Rust-а).
0xd34df00d
15.11.2021 21:11+1Из тех, с чем дело имел я, это еще и чистый Си и Pascal.
В C всё сильно проще (понятие лайфтайма там есть, но понятия конструктора там нет). С паскалем я дел не имел, увы.
А теперь перестаньте изливать свою боль про нагадивший вам в шаровары C++ и попробуйте ответить, почему же в языках с GC нет таких страшных проблем с контролем лайфтаймов.
Потому что их спеку писали не социопаты?
GC помогает только с одной половиной лайфтайма: с удалением объекта. С конструированием объекта GC не помогает, а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как
int
, до C++20 — даже если это кусок памяти отmalloc(sizeof(int))
).И какой ценой в Rust-е попробовали достичь того же без GC (и сколько времени ушло на то, чтобы реальная альтернатива старым языкам без GC появилась в лице того же Rust-а).
Пришлость перестать тарабанить по клавиатуре, наворачивая взаимно противоречащие пропозалы один за другим, и немножко вспомнить, что человечество придумало такую замечательную вещь, как математику, и в этой математике есть несколько релевантных ветвей?
Большая цена, согласен.
Antervis
15.11.2021 23:36Это был буфер, считайте, для обёрток над интами (у которых тоже бывают лайфтаймы, однако).
мне кажется вы просто разную планку ставите. Когда речь о с++, вы считаете что код работает только если он на 100% соответствует стандарту. А когда речь о других языках, то вас совершенно устраивает поведение, определенное одним конкретным компилятором. Так-то больша́я часть UB в с++ вполне себе well defined в компиляторах.
Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.
а еще в них нельзя делать то, что можно в плюсах.
а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как
int
, до C++20 — даже если это кусок памяти отmalloc(sizeof(int))
).удачи сделать это без UB в rust: "
src
must point to a properly initialized value of typeT
" (std::ptr::read_unaligned).
0xd34df00d
17.11.2021 23:23+1мне кажется вы просто разную планку ставите. Когда речь о с++, вы считаете что код работает только если он на 100% соответствует стандарту.
Хоть один компилятор имеет в своей документации фразу «даже невалидный по стандарту код, компилируемый сегодня нашим компилятором, будет компилироваться и иметь то же поведение всегда»? Покуда нет, лучший источник правды о том, как писать плюсовый код — Стандарт, и практика показывает, что компиляторы имеют свойство с новыми релизами ломать старый код и пользоваться ранее игнорировавшимися UB.
В случае же со всякими хаскелями практика показывает, что код в рантайме компиляторами не особо ломается. Я не могу вспомнить ни один такой случай, который при этом не считался бы багом и не был бы связан с вещами вроде
unsafePerformIO
, про которые компилятор явно не даёт никаких гарантий. Собсна, почитайте, например, это и сравните с уровнем UBшности в плюсах.а еще в них нельзя делать то, что можно в плюсах.
Например?
Antervis
18.11.2021 03:56Хоть один компилятор имеет в своей документации фразу «даже невалидный по стандарту код, компилируемый сегодня нашим компилятором, будет компилироваться и иметь то же поведение всегда»?
действительно, гарантирует ли хотя бы один компилятор хотя бы одного языка что они никогда не поменяют поведение ни одной языковой конструкции, специально или нечаянно, и что абсолютно любая программа на одной платформе будет работать в точности так же на другой?
Я не могу вспомнить ни один такой случай...
"не могу вспомнить" и "не было" это разные категории.
... который при этом не считался бы багом
"считается багом с точки зрения компилятора" и "считается багом с точки зрения стандарта" это тоже разные категории. В других языках просто нет последней инстанции в виде стандарта, и в них вас совершенно устраивают гарантии компилятора. Допустим завтра появится стандарт rust - ринетесь ли вы переписывать весь свой код чтобы он стал conformant?
Например?
ну с тем же rust простейший пример - каст
&
в&mut
это сразу UB. А языки более высокого уровня либо в принципе не дают контроля над памятью, либо проще уж писать на плюсах, чем скажем на джаве, но не пользуясь GC.
eao197
16.11.2021 07:21С конструированием объекта GC не помогает, а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как
int
, до C++20 — даже если это кусок памяти отmalloc(sizeof(int))
).И как с этим помогает GC? Я так понимаю, что в используемых вами языках с GC такая операция является штатной и делается на раз-два, не так ли?
mrbald
01.11.2021 11:27+1Пишу на C++ (всё, что понимают последние Clang/Gcc в RHEL). Знаю много хороших трюков и полезных библиотек. Никогда нигде не пишу и не говорю, что знаю C.
Отучить себя от исключений, RAII, кодогенерации с шаблонами и других "удобств" и вместо одной стандартной библиотеки знать все ключевые особенности target platform programming interfaces (POSIX, BSD, etc.) совсем не просто.
warlock13
09.11.2021 03:56-1Неужели есть люди, которые пишут на C++ с включённым RTTI и исключениями? Мне трудно представить такое.
anamnesis875
03.11.2021 12:23+1Был случай...пришел на собеседование (товарищ пригласил). До этого писал на С и всякими аппаратными вещами занимался (но не разработкой с нуля). Сказал перед началом: "На С++ писал только сервисную муть для себя же, так что ничего профессионального не умею". Поспрашивали про области видимости, когда создаются static...и конечно про volatile)) ах, да и про диодный мост, RS422 (почему диф. сигнал такой хороший)...но в конце все-равно начали спрашивать про С++...я ничего не смог, но мне сказали "ничего страшного, вот есть хорошие книги". В итоге их компании прошлось отказаться от С++ в пользу С, ибо там было импортозамещение и памяти не хватало (в это можно поверить) и времени между циклами. Так что некоторое время можно еще пожить как в прошлом) Про компанию ничего конкретного не знаю, ибо остался на своем любимом заводе))
Myxach
17.11.2021 10:37Использует C-style массивы вместо std::array;
Стоп, а на C++ что уже массивы с квадратными скобками устарели?
Ну и кстати, тут многие вещи это результат не "Пришел с си", а "Программиует на C++ больше 10лет и не изучал новые стандарты."
SinsI
18.11.2021 19:26+1Нарушают кучу всего в CPP Core Guidelines - небезопасны, теряют размер при передаче как аргументов функции...
Так что да, устарели.
nick1612
Мне кажется, что такая логика может встречаться только у совсем уж новичков и я сомневаюсь, что хоть кто-то из более-менее опытных программистов на C может так думать или не знать про кардинальные различия между данными языками. Из перечисленного вами списка, многое может не использоваться намеренно, а не потому, что об этом не знают.
В общем очень спорные утверждения.
F0iL Автор
Я тоже раньше сомневался, пока не начал помогать проводить собеседования, и на практике оказалось, что таких каждый второй :(
Об этом я отдельно написал в конце публикации. Естественно, если человек может грамотно обосновать использование или неиспользование той или иной языковой конструкции или апишки для конкретной задачи - то это уже говорит о его понимании и зачитывается только в плюс. Но когда задача весьма прозаична и каких-то особых "обоснований" нет - тогда печаль.
ksbes
Как "программист на С" (в том числе) я подтверждаю. Для меня "современный С++" - тьма непроглядная, в которую мне вникать и вникать. Rust, кстати, в этом смысле проще.
Приходилось "поддерживать код" на новых С++ - писал именно как указанно в статье (ну кроме new/malloc - но только и-за того что неправильно смешивать парадигмы управления памятью в одной кодовой базе)
dromaniak
100% согласен. Поэтому для меня, как для Си-шника, Java, как ООП-продолжение, намного приятней, чем современный C++.
moshamiracle
Подтверждаю. Я даже как начинающая студентка после относительного освоения C, плюсы были сложнее в освоении, чем понять концепции Rust и начать писать на нем. Хотя и понимаю, что работу найти пока будет сложнее.