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 с классами":

  1. Использует <stdint.h>, <string.h>, <stdio.h> вместо <cstdint>, <cstring>, <cstdio>;

  2. Использует malloc() и free() кроме явно предназначенных для этого мест (типа кастомных аллокаторов);

  3. Использует ручное управление памятью с new и delete, вместо RAII и умных указателей;

  4. Использует char*-строки и функции <string.h> вместо std::string и std::string_view. (единственное исключение - строковые константы через constexpr). Использует функции из <time.h> вместо std::chrono. Использует atoi() вместо stoi(). Использует функции из <stdio.h> вместо std::filesystem и потоков ввода-вывода. Использует <pthread.h> вместо std::thread.

  5. Когда нужно имплементировать алгоритм или контейнер независимый от типа данных, которыми он оперирует, использует #define-макросы или void*-указатели вместо темплейтов;

  6. Для объявления констант использует #define вместо const и constexpr;

  7. Использует C-style массивы вместо std::array;

  8. Использует NULL вместо nullptr;

  9. Пишет (type)something вместо static_cast<type>(something);

  10. Использует простые указатели на функции вместо std::function;

  11. Использует константные enum вместо enum class даже для простых перечислений;

  12. Для функций, не изменяющих состояние объектов, не использует const при объявлении. Для конструкторов забывает explicit. Для деструкторов забывает virtual :)

  13. При разработке в ООП-стиле, объявляет все члены класса как public;

  14. Если вам нужно вернуть из функции несколько разных значений (например, результат работы и/или код ошибки), то одно из них возвращает через return, а другое - по указателю или по неконстантной ссылке, вместо использования std::optional, std::pair/std::tuple (особенно хорошо в паре со structured binding) или просто возврата struct;

  15. Объявляя новую переменную с типом-структурой везде пишет struct в имени типа, или наоборот, при объявлении новой структуры пишет typedef struct вместо просто struct;

  16. Не использует неймспейсы при структурировании кода;

  17. Использует union вместо std::variant (кстати, для каламбура типизации использовать union тоже нельзя, он нарушает active member rule);

  18. Пишет реализации общеиспользуемых алгоритмов (foreach, transform, find_if, sort, lower_bound, и т.д.) вручную даже если они есть в <algoritm>;

  19. При простой итерации по элементам контейнера пишет многословные конструкции вместо range-based for. Не использует auto и using в многословных конструкциях типов;

Плюс немного дополнений из комментариев:

  1. Использует битовые поля вместо std::bitset

  2. Использует си-шные библиотеки на прямую без уровня абстракции над ней

  3. В заголовочных файлах куча инклудов, которые можно было в принципе там и не писать (incomplete class)

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

  • у вас может быть очень много соприкосновений с чисто сишными библиотеками;

  • проект может использовать древний тулчейн, умеющий только C++98 (правда, при работе в таких проектах нужно требовать тройную оплату и дополнительную страховку за вредность, а лучше вообще избегать подобного);

  • вы используете Qt, где своя модель владения, и new там используется на каждом шагу;

  • std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);

  • абстракции рано или поздно протекают: вы не сможете создать std::fstream из существующего и открытого posix file descriptor (хотя некоторые реализации stdlib такое умеют), а средствами <thread> вы не сможете задать приоритет потоку, и еще много чего;

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

Комментарии (404)


  1. nick1612
    26.10.2021 13:25
    +26

    Логика у человека простая: языки, на первый взгляд, довольно близкие и почти обратно-совместимые, базовый синтаксис одинаков, про ООП кандидат что-то слышал, и значит, основная база уже есть и он сможет легко освоить C++ за 21 день в процессе работы, поэтому можно наплести про "с C++ тоже работал", начать писать на "Си с классами" и все получится.

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

    В общем очень спорные утверждения.


    1. F0iL Автор
      26.10.2021 13:31
      +8

      я сомневаюсь, что хоть кто-то из более-менее опытных программистов на C может так думать

      Я тоже раньше сомневался, пока не начал помогать проводить собеседования, и на практике оказалось, что таких каждый второй :(

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

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


      1. ksbes
        27.10.2021 12:36
        +5

        Как "программист на С" (в том числе) я подтверждаю. Для меня "современный С++" - тьма непроглядная, в которую мне вникать и вникать. Rust, кстати, в этом смысле проще.

        Приходилось "поддерживать код" на новых С++ - писал именно как указанно в статье (ну кроме new/malloc - но только и-за того что неправильно смешивать парадигмы управления памятью в одной кодовой базе)


        1. dromaniak
          28.10.2021 06:49
          +3

          100% согласен. Поэтому для меня, как для Си-шника, Java, как ООП-продолжение, намного приятней, чем современный C++.


        1. moshamiracle
          29.10.2021 09:19
          +1

          Подтверждаю. Я даже как начинающая студентка после относительного освоения C, плюсы были сложнее в освоении, чем понять концепции Rust и начать писать на нем. Хотя и понимаю, что работу найти пока будет сложнее.


  1. Koval97
    26.10.2021 13:47
    +8

    На месте директора я бы сразу уволил такого менеджера в 24 часа, с пометкой "Больше его к техническим специалистам не допускать". И мне все равно, что всего за один дурацкий заголовок.


    1. F0iL Автор
      26.10.2021 13:48
      +5

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

      А для всех остальных, кто не делает выводов по заголовку, а сначала читает содержимое статьи: если вы тот самый матерый плюсовик-затейник, умеете готовить Yocto и не прочь переехать жить и работать в Чехию - черканите мне, возможно нам есть о чем пообщаться :)


      1. Koval97
        26.10.2021 14:59
        +1

        Я скорее энтузиаст, и предпочитаю использовать максимум API нежели готовые framework-и. Да, я художник, а не биоробот и с такими работаю максимум подрядом. Потому что не работаю с помешанными на перфекционизме диктаторами. Как я уже сказал, без зазрения совести такого менеджера нельзя допускать к разработке, даже если он карьерист и уже Team Lead. Либо ваш диструктивный подход скорее вредит разработчикам, чем помогает. И о вас уже есть целая статья.

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


        1. F0iL Автор
          26.10.2021 15:07
          +4

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


        1. victor_1212
          26.10.2021 16:32
          +5

          >.... когда станете в ходе работы над собой более конструктивным

          imho, все просто, это статья написана не для вас (и не для меня), честно говоря не совсем понимаю для кого она, но это не повод для волнений, опыт всегда прав, но у разных людей в разное время :)


  1. 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. Если вы используете коды ошибок вместо исключений.


    1. F0iL Автор
      26.10.2021 14:20
      +7

      слепо

      А про "слепо" никто и не говорил. Перечитайте последний абзац.

      применять аллоцирующий полиморфный враппер

      Аллоцирующий он далеко не всегда. Пример.

      даже если забыть о том, что половина случаев с указателями на функции — это интероп с C

      Интероп - это отдельная тема. Есть проекты, где чистого интеропа с сишными библиотеками почти нет, а если и есть, то во всех точках соприкосновения написаны C++-врапперы.

      std::function туда не пролезет при всем желании

      Ну, в редких случаях, кстати, пролезет через target().

      Для всех подряд? ... Для всех подряд?

      Естественно, не для всех подряд. Статья есть лишь краткое перечисление пунктов, а не огромный талмуд описывающий подробно все возможные случаи и границы применения. Кто знает - тот знает, кто не знал - откроет cppreference, стандарт языка и какого-нибудь Скотта Майерса и узнает.

      + 21. Если вы используете коды ошибок вместо исключений.

      Во многих проектах исключения вообще выключены. Например, в Chromium.


      1. ncr
        26.10.2021 14:41
        +7

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

        Вы пишете "Как различить C и C++-разработчиков" и приводите безапелляционный список "делай так, а не так". А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".


        1. F0iL Автор
          26.10.2021 14:48
          +7

          безапелляционный

          Перечитайте последний абзац статьи

          А потом дети и сишники начитаются и начнут лепить explicit и virtual куда ни попадя, потому что "это С++, так в интернете написано".

          Ну так если человек делает что-то только потому что так написано в интернете, не понимая зачем и почему, то тут никакие даже исчерпывающие "описания в интернете" не помогут :(


          1. dvserg
            27.10.2021 09:22
            +2

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


            1. F0iL Автор
              27.10.2021 09:32
              +3

              Это совершенно логично. Разве я где-то утверждал обратное?


              1. dvserg
                27.10.2021 09:56
                +1

                Я хотел показать, что поиск кристально-чистого С++ - cника сродни поиска сферического коня. Многие коллеги в своем роде "мутанты" между этими двумя языками. И мне кажется намного важнее в случае рассмотрения кандидатур знание/умение применить перечисленные приемы, чем слепое безапелляционное следование им "я_типа_тру_С++". То есть важно смотреть не то, что по умолчанию применил человек при решении теста, а его способность решить поставленную задачу "с применением таких-то вещей".


                1. F0iL Автор
                  27.10.2021 10:12
                  +3

                   И мне кажется намного важнее в случае рассмотрения кандидатур знание/умение применить перечисленные приемы, чем слепое безапелляционное следование им "я_типа_тру_С++".

                  Да, все верно. Я в статье этот момент специально отметил:

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


                  1. dvserg
                    27.10.2021 10:31

                    Ну возможно я что-то и упустил из Вашей статьи, начитавшись комментариев. ))


    1. Antervis
      26.10.2021 17:31
      +6

      Советовать слепо применять аллоцирующий полиморфный враппер независимо от того, нужен ли он — это шикарно само по себе

      std::function аллоцирует только при наличии контекста (например, захвата из лямбд). Полным аналогом void set_callback(std::function<int(int)>&& cb) в чистом си будет void set_callback(int(cb*)(int), void* ctx, void(*ctx_deleter)(void*)); где ctx в общем случае тоже надо аллоцировать, ну и не забыть удалить. Так что совет "используйте std::function вместо коллбеков когда можете" более чем валидный.


      1. F0iL Автор
        26.10.2021 18:08
        +2

        std::function аллоцирует только при наличии контекста

        И даже при наличии контекста далеко не всегда, во многих реализациях там есть small size optimization. Пример выше уже скидывал.


      1. ncr
        26.10.2021 18:52
        +7

        Бывают каллбеки с контекстом, бывают без контекста.
        Если он там есть — естественно, std::function проще, лучше и понятнее, чем любой ручной колхоз.

        Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.

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


        1. Antervis
          26.10.2021 19:41

          Однако, оверхед там есть и он далеко не нулевой, даже с оптимизациями. И noexcept, как автор рекомендует в 12 пункте, уже не прицепишь.

          Присмотритесь. В примере bar() отлично оптимизируется в noop, пара доп. инструкций в foo() и call std::__throw_bad_function_call() это проверка функции против null (то, чего сишная версия не делает, а если делать, то оптимизируется и там и там) а typeinfo сгенерился для всяких стектрейсов и убирается с --no-rtti. По сути весь оверхед сводится к заполнению std::function при инициализации, да и это важно только если ваш метод вызывает коллбек никуда его не сохраняя


          1. tyomitch
            26.10.2021 19:46
            +5

            Если против прыжка на NULL меня страхует MMU, то я не хочу тратить на проверку на NULL отдельные инструкции. Си позволяет их не тратить; а если хочется тратить, то позволяет и тратить.


            1. Antervis
              27.10.2021 06:43
              +2

              если в конкретном случае вы замерили перф и оказалось что да, лишний if в std::function тормозит, то конечно же, делайте на указателях. Вот только под общий случай такой кейс вообще даже близко не подходит. Ну и от себя добавлю, что на практике я куда чаще сталкивался с недостаточно гибким интерфейсом (и костылями как его следствием), чем с тормозами в хорошо предиктящемся if


            1. sheckn
              28.10.2021 13:19
              +1

              Вынужден вас расстроить: по крайней мере часть компиляторов вставляет эту проверку принудительно,.им им пофигу, что адрес 0x0 находится в секции с флагом x


    1. 0xd34df00d
      26.10.2021 19:48
      +12

      А вы можете доказать, что не кидающих? И что все, используемое в этих функциях, тоже некидающее? И используемое используемым all the way down? noexcept уместен в специальных функциях и критических местах, в остальных — опять же, больше попаболи, чем пользы.

      Зашел в комменты написать именно это.


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


      1. agmt
        27.10.2021 08:54

        А есть объяснение, почему такую проверку не добавят в clang-tidy?
        clang.llvm.org/extra/clang-tidy/checks/bugprone-exception-escape.html ругнётся, если увидит явное кидание исключения (т.е. функции должны быть в header), но вызов `noexcept(false)` функции — не ошибка.
        Казалось бы, проверить noexcept() всех вызываемых — легче, чем анализировать их код.


        1. tyomitch
          27.10.2021 09:28

          1. agmt
            27.10.2021 09:41
            +2

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


    1. eao197
      27.10.2021 08:41

      Если у вас enum'ами задаются конкретные константы, которые часто надо куда-то передавать в числовом виде (тот же интероп), то от enum class больше попаболи, чем пользы.

      Можно ли этот тезис раскрыть более подробно?


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


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

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


          1. Antervis
            27.10.2021 15:18
            +1

            Самый простой способ сделать нормальные флаги в с++ - std::bitset. Можно сделать тонкую обертку для enum class, переопределив operator[]. А все эти битовые трюки с флагами-степенями двоек были не от хорошей жизни придуманы.


            1. eao197
              27.10.2021 15:27

              Тем не менее, когда с таким приходится работать, то почему бы и нет. Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71. Ну и до C++11 в заголовочных файлах константы через std::bitset было не задать, разве что вместо констант писать inline-функции, которые возвращали бы экземпляры std::bitset.


              1. Antervis
                27.10.2021 16:27

                Тем более, что встречаются флаги, в которых должно быть выставлено сразу несколько битов, типа 0x82 или 0x71

                так у bitset'а есть конструктор от числа, можно в обертку добавить конструктор от initializer_list<my_enum>, вариантов уйма


                1. eao197
                  27.10.2021 16:31

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

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


          1. klirichek
            04.11.2021 05:12

            Тип то фиксированный, но общий и вполне интегральный.

            Его наличие никак не помешает написать, например,

            tCounter.store ( std::memory_order_release );

            вместо

            tCounter.store ( 0, std::memory_order_release );


            1. 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 -- это лично для меня большой вопрос.


    1. dimaaannn
      28.10.2021 00:17
      +1

      Мне кажется, плюсовики - это такая секта.

      Они готовы не только спросить о принципах ООП, но и уточнят в каком текстовом редакторе вы обычно работаете. Потому что не все из них "правильные".


      Ну а навыком кодить на бумажке должен обладать каждый уважающий себя программист.


      1. elektroschwein
        28.10.2021 16:52

        Они готовы не только спросить о принципах ООП, но и уточнят в каком текстовом редакторе вы обычно работаете. Потому что не все из них "правильные".

        Ну у вас и фантазии.

        Ну а навыком кодить на бумажке должен обладать каждый уважающий себя программист.

        Вы забыли добавить, "...если его основным видом деятельности является кодинг на бумажке".


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


    1. F0iL Автор
      26.10.2021 14:27
      +2

      и этого нельзя достичь с использованием enum class без повсеместного ручного приведения типов

      Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения. В GCC'шной стандартной библиотеке так, например, сделаны std::launch и std::filesystem::permissions.


      1. grondek
        26.10.2021 14:47
        +10

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

        То есть, как у вас в конце статьи и написано: надо смотреть по конкретной ситуации.


        1. 0xd34df00d
          26.10.2021 19:46
          +2

          Так в кутях и так макросы используются для объявления флагов, добавить туда операторы — мелочь.


      1. Gordon01
        26.10.2021 15:26
        +7

        Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.

        Можно, но зачем?

        А я скажу, что после этого код превратится в легаси и в чем-то буду прав.

        Если завтра к вам придет обычный разработчик, а не фанат плюсовой магии, как вы ему объясните, зачем, это было сделано?


        1. F0iL Автор
          26.10.2021 15:52
          +4

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

          А ответ на вопрос "зачем" будет точно такой же, как и на "зачем вообще придумали enum class": типобезопасность. Когда у вас у функции два аргумента, каждый из которых представляет собой битовую маску, или один битовая маска, а другой вообще какое-то целое число, то перепутав их местами в одном случае вы получите ошибку компиляции, а в другом случае компилятор молча схватает это и скомпилирует программу с ошибкой. Или, например, вы по ошибке сделаете | между членами совсем разных enum'ов. В одном случае это тоже вызовет ошибку компиляции, а в другом случае компилятор промолчит. И более того, совпасть может так, что работать все будет абсолютно корректно (если позиции битов битов для конкретно этих значений в этих разных enum'ов совпадают), но совершенно внезапно сломается когда внесут изменения в один из них. И это только один из примеров.


          1. Gordon01
            26.10.2021 16:48
            +5

            Не, вы меня не поняли.

            Обычный разработчик (не фанат плюсоизмов), на большинстве языков из топ10 не сможет или не будет это делать. Он просто напишет функцию is_read_only().

            Как вы этому человеку объясните зачем вы это делаете:

            Можно перегрузить &, |, &&, ||, ^, &=, |=у этого enum'а и работать с ними как раньше, или даже навернуть темплейт сверху для более универсального решения.


            1. 0xd34df00d
              26.10.2021 19:50
              +3

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


              1. Gordon01
                26.10.2021 20:40
                +3

                А код с бизнес-логикой писать хоть один из этих людей будет?

                • Один отстреливает себе ноги.

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

                • Третий разбирается в причинах трехэтажных ошибок шаблонов из-за того что он написал bit | flag для типа которому оператор | не перегружен его коллегой сверху

                • Последний спорит в комментах на хабре


                1. elektroschwein
                  26.10.2021 20:47
                  +5

                  Если вам нужно без проблем и приключений "писать код с бизнес-логикой", то стоило взять не C++, и уж тем более не C, а упомянутые здесь Java или Python. Ну или Rust, но там придется воевать с borrow-checker'ом.


                  1. Gordon01
                    26.10.2021 21:09
                    +3

                    Нет, нельзя, скрпы эмбеддеров это запрещают!

                    Шутка. Я бы конечно все просто писал на расте. Я пробовал и под МК без MMU писать и под арм64, все отлично, удобно и комфортно.

                    Просто есть отдельная часть людей, которые считают что под МК надо писать только на Си, а под армы с MMU - на С++. Причины и доводы, как обычно, те же самые, но вешать ярлыки не будем.

                    Просто почему-то этих людей совершенно не смущают известные и успешные проекты, которые были сделаны не на С/С++.

                    А как бы мы жили без Java Card - вообще не понятно.


                  1. bonta
                    27.10.2021 07:51
                    +2

                    Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.

                    В общем С++ лучше, к тому же с активным развитием современных стандартов вряд ли Раст особо сможет потеснить ++, т.к. и там приемлемо безопасно и адеквантый синтаксис схожий с Java и C# - а это большой плюс при выборе яп.

                    Например когда Джавистам или Шарпистам потребуется высокая производительность - вряд ли они выберут Rust, но выберут С++ т.к. им его будет значительно проще освоить.


                    1. orion_tvv
                      27.10.2021 21:21

                      у ржавого синтаксис по началу действительно странноватый. но это скорее из-за новых концепций, нежели синтаксических конструкций. аналогичные вещи на плюсах как правило выглядят более тяжеловесно. как яркий пример из относительно свежего оператор <=>


                    1. AnthonyMikh
                      27.10.2021 21:50
                      +2

                      Ещё бы был у это Раста синтаксис адекватный а не вырвиглазный.

                      А что конкретно, по вашему, не так с синтаксисом Rust?


                      1. elektroschwein
                        27.10.2021 22:19
                        +3

                        То, что он вырвиглазый ¯\_(ツ)_/¯ 


                      1. DistortNeo
                        27.10.2021 22:22
                        +1

                        Скорее, непривычный в мире языков с C-подобным синтаксисом.


                      1. AnthonyMikh
                        27.10.2021 22:24

                        Я же спросил конкретно. А то и я могу сказать, что у C++ вырвиглазный синтаксис.


                      1. bonta
                        28.10.2021 07:54
                        +1

                        я написал своё мнение, т.к. лично для меня чем дальше язык по синтаксису от Си++ тем он уродливее (именно от ++, а не от родоначальника синтаксиса – Си). В т.ч. мне не нравятся и Питон например. Но нравятся Джава, Шарп и Делфи (хоть он и не из Си семейства, но за счет многословности – а многословность неплохо описывает сам язык без документации, читать Делфийский код можно без предварительного изучения я.п, что не скажешь о Расте или Питоне например).

                        А вот Си мне не нравится например, причем сильно. Потому что на больших проектах (например Gimp) или там OpenVPN хорошо видно что языку не хватает того что есть в ++, в следствии чего например усложненные интерфейсы по сравнению с ++ (по кол-ву входящих параметров), обилие похожих друг на друга интерфейсов, отличающихся префиком/постфиксом (т.к. нет шаблонов и перегрузок - то часто на каждый тип входящих параметров или возвращаемых значений делают отдельный интерфейс).


                1. 0xd34df00d
                  27.10.2021 04:31
                  +8

                  Насчёт этих людей не знаю, но для себя я как-то понял, что единственный способ сохранить рассудок — не писать на плюсах.


      1. 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 — это «хотели как лучше, а получилось как всегда».


        1. Gordon01
          26.10.2021 17:17
          +2

          Вообще не особо понятно, зачем автор предлагает из монструозного и сложного С++ костылями в одном месте сделать простой и лаконичный С, если до этого целую статью объяснял какие сишники смешные, что не хотят менять свой работающий подход кодирования определенных вещей (совместимый по стилю и семантике с другими популярными языками) на очередной новый стиль С++420.


          1. F0iL Автор
            26.10.2021 17:24
            +4

            о не хотят менять свой работающий подход кодирования определенных вещей

            Атож. Напоминает классику: "Программист на Фортране пишет на Фортране на любом языке".

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

            Вообще не особо понятно, зачем кто-то предлагает из монструозного и сложного C транслятором в одном месте сделать простой и лаконичный ассемблер (привет из 70-х).


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


    1. apro
      26.10.2021 15:54

      В Qt есть Q_FLAGS/Q_DECLARE_FLAGS для генерации типобезопасной обертки над enum class описывающим флаги.


      1. 0xd34df00d
        26.10.2021 19:56
        +1

        Когда я последний раз ковырял Qt, там были обычные enum, и с enum class оно не особо работало. Может, в шестерке поменяли, впрочем.


  1. KotKotich
    26.10.2021 14:28
    +12

    Опять евангилист всяких догм. Инструменты нужно подбирать в зависимости от задачи. А не применять тупо заученные догмы и запреты. Правильно вышестоящий комментатор написал "На месте директора я бы сразу уволил такого менеджера в 24 часа, с пометкой "Больше его к техническим специалистам не допускать"."


    1. F0iL Автор
      26.10.2021 14:28
      -3

      Бьёрна Страуструпа вы тоже "евангелистом всяких догм" назовете? :)

      А если серьезно, то перечитайте, пожалуйста, внимательно последний абзац статьи, а то не смешно уже.


      1. tyomitch
        26.10.2021 18:36

        Бьярне (Bjarne); Bjørn -- тоже существующее имя, но другое.


        1. F0iL Автор
          26.10.2021 18:39

          Буду знать. Интересно, но в русскоязычном сообществе устоявшимся написанием почему-то стало "Бьерн", видимо, стараниями издательств: https://www.ozon.ru/person/straustrup-bern-253179/category/knigi-16500/


  1. Rustacean
    26.10.2021 14:40
    +1

    интересно, через сколько в комментарии к статье придут любители Rust?

    Явная провокация. Ну вот к чему это было? Не зря в комментариях в основном критическое восприятие статьи.


    1. F0iL Автор
      26.10.2021 14:44
      +2

      Где провокация-то? Так, маленькая дружественная шпилька в сторону любителей Rust'а :) Что, впрочем, не отменяет факта, что Rust - прекрасный язык (ну, кроме его синтаксиса, имхо).


  1. SShtole
    26.10.2021 15:01
    +28

    Используете функции из <time.h> вместо std::chrono.

    Сразу вспомнил вот эту картинку:

    image


    1. funny_falcon
      26.10.2021 21:02

      C: gettimeofday(), clock_gettime() . Уверен, что и другие способы найдутся.


      1. vient
        26.10.2021 23:40
        +5

        Обе эти функции не из стандартной библиотеки C, на Windows, например, они не существуют.


        1. funny_falcon
          27.10.2021 00:58
          -1

          Но по факту, time уже почти ни кто не использует. Кроме уж совсем простых случаев. Если есть возможность, используют clock_gettime, т.к. только он возвращает MONOTONIC. Если реализация POSIX уж слишком древняя, мирятся с gettimeofday и возможным дрифтом часов, но зато хоть миллисекунды получаю

          На Windows наверняка тоже есть способы. Легкий Гуглинг показывает GetSystemTime, GetSystemTimeAsFiletime и GetSytemTimeAsPreciseFileTime. И для монотонного времени QueryPerformanceCounter и QueryPerformanceFrequency .

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

          Кстати, POSIX - это стандарт, или не стандарт?
          Формально - стандарт. А по факту :-(


  1. SShtole
    26.10.2021 15:16
    +27

    А по теме: мне давно кажется, что в индексе TIOBE на четвёртом месте находится не C++, как там написано, а совершенно другой язык — C/C++. Допустим, мне понадобился SQL-движок с максимально шустрыми кастомными агрегирующими функциями. Я беру SQLite. При этом, в обвязочном коде используются и классы, и шаблоны, и ещё кой-чего, и в целом получается, что модуль написан на C++. Но на самом деле меня интересовал способ воспользоваться чисто сишной библиотекой, а эту архитектурную астронавтику из chrono оставьте себе, спасибо.

    Ещё мне кажется, C++-ные пуристы со своими наездами не понимают, до какой степени их язык паразитирует на сишном фундаменте. Убери его — и язык из топа быстро скатится на дно рейтинга. Рядом есть другие языки и у них стандартные библиотеки не такие упоротые.


  1. shybovycha
    26.10.2021 16:04
    +26

    Я настолько еретик, что пишу в резюме C/C++/C#


    1. funny_falcon
      26.10.2021 21:03
      +2

      Чернокнижник!


    1. atd
      26.10.2021 22:25

      У меня есть пара проектов, где в C# unsafe ставится целиком на классы, причём почти на все. Вот там C/C++/C# вполне валидная категория )))


      1. AquariusStar
        26.10.2021 23:09

        А я наоборот. Пытаюсь засунуть привычные мне реализации ООП C# в язык Си. В основном, это работа со строками. C# unsafe я тоже вполне применял в одном проекте.


  1. 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 проверять строчку форматирования во время компиляции сводило преимущество в типизации к нулю. Что-то изменилось?


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


      1. lepota
        27.10.2021 09:37

        3) умеет в итераторы, то есть совместим с 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));


        1. F0iL Автор
          27.10.2021 09:42

          Да, здесь сработает, вариант с ручным std::begin/std::end я не учел.

          А вот если вам передали массив просто как указатель, например int*, как это любят делать в сишном коде, то все становится гораздо сложнее :)


    1. 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++ потоков нигде нет.


    1. 0xd34df00d
      26.10.2021 19:58
      +3

      Так потоки ввода/вывода в C++ были всегда тормознее Cшных вариантов.

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


      Впрочем, какой-нибудь буст.спирит вообще всех рвет.


  1. JaroslavTavgen
    26.10.2021 16:17
    -6

    опытный плюсовик-затейник, который будет активно внедрять best practices

    Переход на Java, Python или Javascript?


    1. F0iL Автор
      26.10.2021 16:17

      Для embedded-разработки, ага. Добавьте еще Golang и C# в список.


      1. JaroslavTavgen
        26.10.2021 16:31

        Ну если память не экономим и скорость не оптимизируем (или оптимизируем?), то можно хоть PHP, нет?


        1. Gordon01
          26.10.2021 20:04

          Скрепы плюсовиков-эмбеддеров это категорически запрещают


      1. alexeishch
        26.10.2021 17:48
        +2

        C# вот отлично работает в embedded. Пробовал на плате с 4ядерным ARM процессором и 2 Гб оперативы. Нужно было сделать MVP, который потом подрядчик не сильно улучшил переписав на C++. Работать стало конечно быстрее, но конечный потребитель разницы не заметил.


        1. F0iL Автор
          26.10.2021 18:16
          +2

          Ну тут все зависит от целевой платформы. Когда у вас какой-нибудь STM8, то придется ужиматься по-полной, когда у вас 4 ядра мощных и 2 ГБ оперативы, тогда можно развлекаться как душа пожелает, а когда у вас 500 мегагерц и 256 мегабайт, то шикануть уже не получиться, но и каждый байт и каждый такт считать не обязательно.


          1. Gordon01
            27.10.2021 11:52
            +2

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

            У меня когда-то был компьютер с amd k6-2/500 и 128 мегабайтами памяти. Так вот там был сайт на пхп с mysql, еще кучка сервисов, которые обслуживали около сотни человек.

            Современный ПХП еще в несколько раз быстрее той пятой версии, что я тогда использовал.

            Как-то вы совсем недооцениваете возможности железа.


            1. Areso
              28.10.2021 02:50
              +1

              Только за продшие 20 лет не только интерпретаторы и компиляторы стали эффективнее, но и прикладной софт разбух до неимоверных масштабов.

              Вы сегодня на 256 метрах ОЗУ вряд ли классический LAMP стек с какой-либо существенной полезной нагрузкой сможете развернуть.


        1. Areso
          27.10.2021 21:44
          +4

          2 ГИГА ОЗУ в Эмбеддед.

          Чтобы я так жил.

          Да вы там Ноду, прости господи, ДжиЭс можете использовать.

          А у меня 32 КиБ ОЗУ и лапки.


          1. alexeishch
            28.10.2021 01:53
            +1

            Хм, в моём мире Embedded - это встраиваемые решения. Типа банкомата, бортового компьютера и прочее, на них может работать линукс. 32 кб ОЗУ - это микроконтроллер


            1. Areso
              28.10.2021 02:52

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


      1. lromanov
        26.10.2021 19:33
        +2

        Вот вы смеётесь, а существует и довольно популярен python для микроконтроллеров: MicroPython


        1. Areso
          27.10.2021 21:45

          На нём довольно удобно писать всякое для квест-комнат.


    1. victor_1212
      26.10.2021 16:48
      +5

      >опытный плюсовик-затейник

      как-то не пришлось таких встречать, приходилось видеть:

      либо опытный плюсовик,

      либо плюсовик-затейник,

      либо опытный затейник,

      но это конечно не значит, что такое животное не существует, просто пока не обнаружено :)

      как обычно, imho


      1. F0iL Автор
        26.10.2021 16:50

        либо опытный плюсовик,

        либо плюсовик-затейник,

        либо опытный затейник,

        Кстати, если собрать всех троих в одной команде, то может получиться отличное комбо!


        1. victor_1212
          26.10.2021 16:57

          Возможно Вы имеете в виду gumbo?

          https://www.neworleans.com/restaurants/traditional-new-orleans-foods/gumbo/


        1. Arcanum7
          27.10.2021 10:15
          +1

          Скорей как начало анекдота. Возможно даже про поручика, да.


  1. olekl
    26.10.2021 16:25
    +2

    Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение? Особенно применительно к микроконтроллерам и штукам класса зарядка для электромобиля? Где гораздо уместнее смотрелось бы RTOS+MISRA C хотя бы...


    1. F0iL Автор
      26.10.2021 16:39
      +2

      MISRA есть не только для 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 тут является оптимальным решением для достижения гибкости и минимизации велосипедостроения.


      1. olekl
        26.10.2021 16:58
        +2

        Если в самой зарядке RTOS тогда вопросов нет, коммуникационный узел на Linux как раз понятно, что будет оптимальным. Я просто попробовал представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.


      1. ECRV
        26.10.2021 18:17
        +7

        Наконец из вашего текущего комментария стала понятна простая вещь. Вы пишете под Linux. В область Embedded разработки входит еще и огромный пласт разработчиков под мк и их вы не убедите использовать C++ в своих проектах, ибо под всеми контейнерами уже не поймешь сколько у тебя памяти и на что ушло. Напишите в шапке или вначале статьи что вы имеете ввиду только Linux и половина хейта к вам отвалится автоматом. Под камень с MMU всем очевидно что нужно пользоваться всеми прелестями готовых плюсовых контейнеров.


        1. F0iL Автор
          26.10.2021 18:21
          +1

          Напишите в шапке или вначале статьи что вы имеете ввиду только Linux и половина хейта к вам отвалится автоматом.

          Вообще-то об этом было изначально прямым текстом сказано в начале статьи, аж в третьем по счету предложении:

          Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++

          Не помогло.

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


          1. ECRV
            26.10.2021 18:24
            +1

            Да, вы правы

            Я сам каким-то чудом упустил этот момент. Попробуйте жирным выделить (и убрать Embedded), но видимо это и правда не поможет


            1. Arcanum7
              27.10.2021 10:14
              +3

              "Умом я понимаю что лапа Шарика на плече, но ср@ть оставиться уже не могу."

              (с) анекдот.


        1. olekl
          28.10.2021 17:00

          Я однажды заглядывал внутрь с++ фреймворка Mbed для STM32, когда оказалось, что изменение GPIO пина с Input на Output занимает на нем 600+ тактов... Всего-то несколько слоев классов, да.


          1. allcreater
            01.11.2021 13:17
            +1

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

            Идиоматичные обёртки на C++ могут быть вполне себе zero-cost, на хабре есть несколько статей на тему, вот одна из них.

            С другой стороны, (не знаю, как сейчас, но как минимум несколько лет назад) STMовский HAL был весьма тормозящей либой, даром что сишной и даже написанной производителем железок.


      1. Arcanum7
        27.10.2021 10:12

        Вот это пояснение что, где и как используется очень не хватает в статье. Без этого пояснения статья выглядит как минимум тонким троллингом и слегка провокацией.


        1. F0iL Автор
          27.10.2021 11:16
          +4

          В статье нигде не сказано "пишите только на C++". И нигде не сказано "пишите под embedded только на C++". Так "что, где и как" у нас используется к статье особо отношения не имеет. Мысль другая: если под ваш проект и требования хорошо подходит C - пишите на C, если хорошо подходит C++ - пишите на C++. Но имейте в виду, что С и C++ - при кажущейся внешней схожести, языки очень разные, а попытка перепрыгнуть с одного на другой за пару дней приведет к тому, что вы будете писать не на C++, а на "C с классами". Многие почему-то это не понимают.


    1. victor_1212
      26.10.2021 17:10
      +1

      >Понятный и надежный код, Embedded Linux, C++ - вы серьезно это все поместили в одно предложение

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

      как обычно imho

      ps

      >представить себе Linux, управляющий 30 кВт зарядкой, особенно когда что-то пошло не так.

      как насчет spacex в случае когда что-то пошло не так?


    1. Albert2009ru
      27.10.2021 11:03
      -1

      Ещё и с аппаратным Watchdog'ом... ;))))))))))))))))))))))


  1. Laryx
    26.10.2021 16:47
    +2

    Хм... Оказывается, я "матерый плюсовик". С большинством из пунктов я согласен, но с некоторыми конкретно хотел бы поспорить.

    Реально я "олдскульный плюсовик". На "чистом С" почти не писал, только в студенчестве, в самом конце 80х. Основной опыт - середина 90х - конец 2009, подавляющее время работы писал на MSVC++, в то время с библиотекой MFC. Сейчас много нового, смотрю, но, изучать уже не буду... Вобще от программирования отошел...


  1. Xadok
    26.10.2021 17:08
    +6

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


    1. Arcanum7
      27.10.2021 10:09

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


  1. realimba
    26.10.2021 17:46
    +3

    В Quake3 от C++ только классы но хуже он не стал. Уже сто раз видел проЭкты с невероятными мета гуру C++ которые через пол-года убегают оставляя после себя "чукча писатель не читатель".


    1. F0iL Автор
      26.10.2021 18:10
      +4

      И каждый второй мнит себя Кармаком, хотя даже близко на него не тянет, ага.

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


      1. Am0ralist
        28.10.2021 11:50

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


    1. 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, и воленс-неволенс вынуждена досмотреть этот цирк до конца — так и до тех доковырялись. Плохо, дескать, летаем. Низэнько.


  1. W_Lander
    26.10.2021 22:35
    +1

    Вы собираете свои emb проекты на ядре linux, написанного на С. Т.е. могу предположить, что как и в большинстве подобных проектов это уже 80% кода. Туда еще можно положить немаленький u-boot или его альтернативу. А потом заявляете, что мы вот, без обид образно говоря ), по CAN гоняем 10 кодограмм и десяток алгоритмов бизнес-логики крайне мемори и типо безопасно т.к. не контрибьютим ни строчки "warning: use of old-style". Так у вас де-факто 90% кода уже в опасности ). Вам надо срочно переписывать ядро! По крайней мере загрузчик уж точно можете написать, он же проще пареной репы.


    1. F0iL Автор
      26.10.2021 23:19

      Зачем? Человеко-лет на переписывание ядра потребуется слишком много, игра не стоит свеч. Пока что и ядро и загрузчик вполне себе удовлетворяют всем требованиям.

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


  1. Viktordp
    26.10.2021 23:10

    Поэтому в проекте разрабатывают под Embedded Linux и в качестве основного языка используют C++

    Есть Cortex M4. 144 мгц и 160 к ОЗУ на борту. Необходимо поддерживать GSM модуль, FM микросхему. С FM Постоянно мониторить звук с эфира на предмет выловить кодовую посылку DTMF. В это же время обслужить микросхему Ethernet. При необходимости обслужить флеш память. По двум, трем каналам АЦП нужно мониторить звук на предмет поступления кодовой посылки DTMF. По команде транслировать звук из выбранного канала на УНЧ, или на выбранный выходной канал. Звук для обеспечения приемлего качества должен быть хотябы 24к семплов в сек.. Также необходимо мониторить солнечную панель и заряд аккумуляторов. И все это работает в режиме реального времени 24 часа в сутки. То о каком Ebedded Linux Здесь может быть речь? Только RTOS. Динамическая память не получается, так как некогда собирать мусор в памяти.


    1. F0iL Автор
      26.10.2021 23:13
      +1

      А причем здесь это-то? :) Для вашего конкретного проекта Linux не подходит, вы используете RTOS. А есть немало embedded проектов и устройств, где наоборот Linux вписывается очень хорошо, позволяя достичь гибкости и минимизировать велосипедостроение. Например тот, про который идёт речь во введении этой статьи.

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


    1. F0iL Автор
      26.10.2021 23:28
      +1

      Динамическая память не получается, так как некогда собирать мусор в памяти.

      Динамическое выделение памяти не обязательно связано со сборкой мусора, там вполне может быть RAII и reference counting.

      В RTOS нередко используют блочные аллокаторы и пулы (например, в ThreadX и FreeRTOS), но вообще при правильном подходе даже с использованием классических аллокаторов можно не бояться временных затрат на выделение/освобождение в куче и не пугаться фрагментации, подобающим образом реализованные malloc() и free() выполняются за постоянное время и даже с предсказуемой фрагментацией кучи.


      1. F0iL Автор
        27.10.2021 00:40

        Поправка: не во FreeRTOS, а в uC/OS , перепутал :)


  1. altervision
    26.10.2021 23:26
    +15

    Как различить C и C++-разработчиков по их коду? Элементарно. У одного код написан на С, у другого на С++. С уважением, Капитан Очевидность.


    1. F0iL Автор
      26.10.2021 23:36
      -3

      Учитывая, что формально C++ является надмножеством C (за исключением пары моментов), то вопрос определения не такой уж и очевидный. Иначе бы не было этой статьи :)


      1. NN1
        27.10.2021 00:03
        +2

        Мы о каком C говорим ?

        C99 или C11 или C2x ?

        Количество "пары моментов" не так уж и мало.


        1. F0iL Автор
          27.10.2021 09:22

          Я бы сказал, об ANSI C++ 90-х годов и C89. Тогда там действительно количество "моментов" было в районе пары пары, если не считать те из них что уже тогда были анахронизмом даже в самом Си (типа K&R function definitions).

          Естественно, потом пути языков начали расходиться сильнее (особенно после выхода C11 с VLA и restrict), но тем не менее и по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациями.


          1. Xadok
            28.10.2021 14:04
            +1

            по сей день нередко можно встретить Си-программу которую удастся скомпилировать C++-компилятором вообще без проблем или с самыми минимальными модификациям

            Тут вы можете сильно ошибаться. Что разрешено в С, часто включается в С++ по умолчанию в msvc или через gnu-extensions, но валидным С++ кодом не является, отсюда получается то, что в Сях ок, в плюсах - UB. Оно скомпилируется да, но работоспособность ничем не гарантирована.


  1. Daddy_Cool
    26.10.2021 23:55
    +1

    Вчера как раз размышлял на подобную тему, и даже придумал термин — «ассемблерное мышление». Т.е. изучаем элементарные операции, а дальше их комбинируя создаем всё что нужно. На курсах по асму нам объяснили ввод, вывод, арифметику. Преподаватель говорит — «Ну вот теперь вы можете написать Тетрис или Ксоникс». ИМХО, проблема изучения языков относительно высокого уровня (как С++), что когда знаешь Си, непонятно зачем нужны все эти плюш-плюшки. Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл. Видимо надо начать писать что-то более сложное, чтобы возникла необходимость и я увидел как что-то легко и изящно делается на ++ и долго и муторно на чистом Си.


    1. Aldrog
      27.10.2021 18:06

      Ну впрочем я пишу в основном что-то алгоритмическое — из файла данные взяли, посчитали, запихнули обратно в файл

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


    1. bamovetz
      27.10.2021 20:47
      +2

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

      Я преписывал код С на плюсы несколько раз именно из за того что при малейшей модификации вылазило куча проблем. Классический пример это освобождение памяти, когда на плюсах, при условии выполнения нескольких правил, можно вообще не задумываться об освобождении памяти при выходе из блока. А вот в С надо обязательно отследить все динамические переменные и их освободить. А когда внутри кода несколько ветвлений то освобождение надо делать в нескольких местах либо усложнять код чтобы все сводилось в одну точку.


      1. yeputons
        27.10.2021 22:35

        либо усложнять код чтобы все сводилось в одну точку.

        А goto в нужную точку в хвосте функции для освобождения не помогает, как в ядре Linux часто делают? Или ресурсы часто не так линейно вкладываются?


  1. warlock13
    27.10.2021 00:13

    Я не великий гуру C++, но стараюсь по мере сил. И мне список понравился. У меня лично есть некоторые вопросы к пункту 10* и очень большое сомнение в пункте 16: всё-таки неймспейсы в C++ - это совсем-совсем не то же самое, что неймспейсы в C# или Java. Они созданы для решения конкретной проблемы (конфиликт имён при линковке и сопредельных ситуациях), не предназначены для структурирования кода и плохи в этой роли. В общем, моё текущее мнение (оно может поменяться в будущем с новым опытом): пункт 16 неверен не в каких-то отдельных случаях, а прямо совсем, категорически.

    * даже в том же Rust я обнаруживаю, что очень часто fn (Rust-аналог сырого указателя на функцию) предпочтительнее Fn (Rust-аналог std::function), и я говорю не об интеропе.


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


      1. warlock13
        28.10.2021 04:11

        Я ничего не путаю, я имею в виду разумеется `Box<dyn Fn...>`, и это по-моему очевидно из контекста.


  1. IGR2014
    27.10.2021 00:54
    +6

    Зачем такой спор в комментариях? Элементарно - пишите на C - вы молодец, оставьте его себе и пользуйтесь на здоровье.

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


  1. YuriPanchul
    27.10.2021 03:10
    +2

    Я это прочитал и подумал: боже, как хорошо, что я в отношении С/С++ остановился на стандарте в районе 1994 года (то бишь после templates но до exceptions), и вообще что сейчас проектирую хардвер на Verilog-е, а не пишу на том ужасе, в который превратился C++.

    (При том что я был в прошлом автором продаваемого компилятора с C/C++)


    1. F0iL Автор
      27.10.2021 10:30
      +6

      Тут каждому свое. Я бы наоборот, в проекте, где используется C++ старше C++11 согласился бы работать только за двойную-тройную оплату, и то очень хорошо подумав перед этим :)


  1. DoubleBY
    27.10.2021 09:03
    +1

    Весьма странная статья. И странные комментарии. Мягкие. Не по мощам елей. На мой взгляд, статья граничит с признанием автора в профнепригодности. Кто то тут способен спутать процедурного и объектного программера? Цирк ёёёё... Не, я конечно понимаю, круг ваших задач ограничен рабочим окружением, но все таки... Ограниченность задач играет с вами дурную шутку дружище. Сложность низкая, объемы кода видимо не высокие, всегда можно прошивку с нуля переписать и не нужно париться с кодом доставшимся в наследство))). А вы возьмите и создайте два варианта даже простенькой игры с GUI на С и на С++ а потом передайте своему коллеге с приколом внести в программу новую фишку. И после этого уже наверное можно вудет с вами обсуждать похожести языков. И все таки настолько не интересоваться программированием, что бы не выйти за рамки рабочей рутины, и не попытаться сделать что то еще, не попытаться получить другой опыт? Я сочувствую той организации где работает автор. Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот. И эти философии у людей закладываются годами. И именно с инерцией изменения философии человеческой связана сложность освоения новой парадигмы, а не с новым языком. Новый синтаксис ты можешь пробежать глазами и начать использовать за весьма короткое время, но создавать продукт промышленного качества... Оооо - Нет. Как показывает практика, подавляющее большинство работничков не достигает уровня понимания достаточного для самостоятельной работы в принципе. Они так и остаются на уровне кодеров, которым лиды нарезают задачи и контролируют каждый их шаг. Увы.


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

      Чем похожи С и С++? Кроме синтаксиса и сопоставимого машинного кода на выходе компилятора - они похожи мало чем - буквами. Это два разных инструмента. Они созданы для работы в разных парадигмах и для решения разных задач. И философии программистов их использующих - не сопоставимы ни в кривь ни в кось: почти все что хорошо для С, есть ужас в С++ и наоборот.

      В точку. Именно об этом эта статья, и именно эту мысль я несколько раз в разных формах в этой статье и повторял.

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

      Все верно. И еще раз: именно об этом эта статья, и именно эту мысль я несколько раз в разных формах в этой статье и повторял.


      1. DoubleBY
        27.10.2021 13:02

        Возможно я просто чего то не понял. Без обид. Я тоже лох порядочный и не претендую на что то великое. Но. Ты возьми дезассемблированный код, или посмотри машинные коды в PE. что ты там увидишь? Объекты? Процедуры? Не будет их там. Там будут пушенья параметров в стэк и условные/безусловные переходы к адресам. Поэтому, все костыли в виде языков программирования это для нас, для людей. Еще раз повторюсь. У нас разночтение возникло из за разных объемов и наследия. Ничего удивительного нет, что С++ похож на С. С++ выродился, мучительно и тяжело выломался из Си под воздействием обстоятельств, а именно - под давлением возрастающей сложности создаваемых систем. Когда у тебя проект объемом в пол года - год работы коллектива, и ты не имеешь права на ошибку, приходят совсем другие требования. Просто я с Си начинал развлекаться еще в девяностых, потом был С++, потом С#. И в моей голове объектный и процедурный стили никак не смешиваются даже теоретически, как масло и вода, несмотря на обманчивую схожесть синтаксиса и наличие функций/процедур. Я уже давно не смотрю на свои поделки глазами кодера. Как правило, я сначала достаточно долго хожу вокруг новой поделки ковыряяся в носу (I pick in a deep in my big nose), и поковырявшись исторгаю из себя зачатки жизнеспособной архитектуры, обычно это DDD. И тут я никак не могу допустить, что можно коим то образом я могу недопонять где у меня будет линейное решение, а где нет.


        1. klirichek
          28.10.2021 06:35
          +1

          "Пушенья и переходы по адресам" это всё же не про парадигму, а про компилятор. Ну и про известную идиому (применимую ко всем подобным языкам) о том, что преждевременная оптимизация - зло. Да, всё превращается в машинные коды конкретного процессора. Но изначально всё идёт из выразительности языка и других плюшек. И хорошо, когда для перехода от объектов к процедурам _не нужно_ менять язык, а можно просто писать в другой парадигме на том же языке.

          А вот, например, использование COM (неважно, на С или С++) - в дизассемблере вполне видимо поверх всех "пушений и переходов" (а если есть ещё и отладочные символы - то это вообще очень сильно помогает в reverse engineering). Так что в этом смысле в PE вполне можно увидеть и процедуры, и объекты.


        1. Kotofay
          29.10.2021 23:28

          Мне кажется, что на С объёмные проекты вполне удаются. Например ядро Линукс.

          Ну или Windows на худой конец.


    1. Antervis
      27.10.2021 10:05
      +2

      Кто то тут способен спутать процедурного и объектного программера?

      вы наверно не слышали про то, как на плюсах пишут в процедурном стиле или как на си пишут в объектно-ориентированном (пример)? Очевидно что использовать ООП-парадигму в плюсах сильно удобнее чем в си, но и любую другую тоже. В общем, давайте поскромнее со всеми вашими профнепригодностью, ограниченностью, уровнем кодера...


      1. bamovetz
        27.10.2021 20:57
        +2

        Вот в этом и проблема. Для использования ООП парадигмы надо таки иметь поддержку языка иначе будет очень больно.

        Я считаю что использование ООП в pure C это ересь и то что например нагородили в Gnome это ужас. Такое получилось только потому что чистые С-программисты решили доказать миру что на С вполне пишется большая GUI-система. Все что там нагромождено в ООП-стиле - на порядки удобнее и проще и главное безопаснее делать в С++.


    1. klirichek
      28.10.2021 06:04
      +1

      Кто то тут способен спутать процедурного и объектного программера?

      ...

      Они созданы для работы в разных парадигмах и для решения разных задач

      Так вы не языки сравниваете, а парадигмы. Можно вполне писать на плюсах в процедурном стиле, и это таки будут плюсы, а не си. Он (С++) мультипарадигменный, всё стерпит. Если про С ещё можно сказать, что там сложно в ООП, то вот говорить, что на С++ нельзя в процедуры или даже ФП - уже звучит странно.


  1. Arcanum7
    27.10.2021 10:06

    Умножаемые комментаторы, а вот такой вопрос:
    есть чистый сишный проект, надо сделать что-то где без классов(т.е. без ++) никак от слова совсем.

    Насколько "глубоким" проникновение ++ в тело Си проекта допустимо?

    Насколько будет правильно писать "до упора" на Си но когда уже никак - то ++, но по самой минималке насколько это оправдано?


    1. dvserg
      27.10.2021 10:26
      +2

      А либы не спасут? Основной проект на С, всякие расширения "где без классов никак" - в проект DLL/LIB. Мне кажется более правильным при необходимости до упора следует применять принцип совмещать, но не смешивать ( принцип Бонда ).


    1. wkia
      27.10.2021 11:40
      +1

      При желании, вы можете вынести необходимую функциональность в отдельную либу, написанную на с++, как предложил @dvserg, но здесь начинаются вопросы по ограничениям на линковку? Сможете ли вы линковать свой си-проект со стандартными либами с++ (статически или динамически), если понадобится?

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


    1. lorc
      28.10.2021 00:51

      В ядре линукса есть классы и объекты. На чистом С, да.


      Может вам не так уж и нужен С++?


  1. DummyBear
    27.10.2021 10:58
    +4

    Я когда попробовал на с++ пособеседоваться вроде честно поменял все printf на cout <<, и даже насоздавал каких-то классов, но меня всё равно спалили и сказали, что пишу на си. :(

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


  1. wkia
    27.10.2021 11:23

    std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);

    Очень много чего не подойдет, когда вы не можете работать с динамической памятью. Легче перечислить, что подойдет.


  1. ECRV
    27.10.2021 11:45
    +5

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

    Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день. Это прямо не говорится и для Автора очевидно он то знает плюсы, но не очевидно для старой школы. Я то как раз сам прифигел при переходе на новые плюсы (не относится к Qt), и пока не проникнешься - не поймешь.

    Автору респект, ты двигаешься в правильном направлении и в правильном двигаешь остальных. Люди, которые вопят про "непрофпригодность" не делали проектов на новых плюсах с учетом всех твоих пунктов и всех нововведений, и им просто непонятно то что тебе уже очевидно. Им кажется, что ты готов выкинуть хорошего программиста на Си (коими все себя считают) только из-за того что "Плюсы лучше". И многие программисты на Си думают что и плюсы тоже самое. Когда-то так и было, но не сейчас, и отличия есть как минимум в ваших пунктах.

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

    Еще раз лучи поддержки Автору


    1. F0iL Автор
      27.10.2021 12:58
      +3

      Спасибо. Вы все правильно поняли.

      Я, на самом деле, немного недоумеваю. Такое ощущение, что многие комментаторы вообще не дочитывают публикацию до конца, а сразу же бросаются писать гневные комментарии, сливать карму (аж на -8 слили, лол) и минусовать статью. При том что я изначально обозначил, под какую платформу мы пишем и почему так, и во второй половине статьи отдлельно, прямым текстом и жирным шрифтом отметил, что всё описанное выше имеет свои границы применения и исключения, и все зависит от конкретной ситуации и конкретного проекта. Но люди почему-то этот абзац игнорируют полностью, начинают докапываться до частностей и даже почему-то называют утверждения в статье "безапелляционными", хотя все ровно наоборот :(


      1. ECRV
        27.10.2021 13:43
        +4

        Как мне кажется, программистов на Си сильно больше чем на плюсах, тем более чем хороших программистов на плюсах. И преимуществ у Си много, но они постепенно пропадают. Из реальных областей которые у нас, остались это: малоресурсные мк, низкое потребление или драйвера. Сейчас внутри копеечного мк esp32 памяти столько, что ее даже толком и не считают. За 10 баксов можно взять готовый одноплатник на Linux с более-менее отлаженными драйверами и впихнуть в него даже arm-контейнеры.

        Время разработки проектов сильно сократилось за последние 10 лет. Помню пару лет назад читал статью на хабре где посыл был примерно такой:

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

        При том что уже тогда любой школьник мог просто взять arduino набор за 1000 рублей и все сделать за один вечер скачав библиотеку из интернета, автор той стать упорно все делал с нуля. Уже тогда было понятно что целый класс, невероятно полезных в свое время, людей превратился в набор староверов, мешающих прогрессу и даже теряющих друзей в угоду своей вере. И этот класс страдает от того что их, таких полезных людей, не берут на вакансии С++, хотя казалось бы, каких-то 10 лет назад вообще проблем никаких не было. Страдает, от того что сам понимает что мир шагнул вперед, а они остались на месте. Вы лишний раз напомнили нам об этом и получили вагон минусов.

        Вот примерно так думаю я


        1. Areso
          27.10.2021 21:52

          у Arduino (особенно у дешевых типа Uno, LilyPad) ресурсов тоже как бы не сильно дофига.

          Другое дело, что наколхозить на них относительно рабочее решение вполне реально за 1 выходной.


    1. saterenko
      27.10.2021 14:02
      -2

      Мир шагнул дальше. То что делается на си, за неделю, а раньше и на плюсах делалось за неделю, сейчас на плюсах делается за день.

      Только если говорить об абстрактных задачах, типа: "на голом языке получи из файла строки, отсортируй их и сохрани в другой файл", потому как в плюсах есть готовые структуры в std.

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

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

      Я долго разрабатывал на C и у меня накопился некислый багаж как сторонних библиотек для решения задач, так и собственных решений под все нужды проектов в моей сфере (системы управления рекламой). И из этого конструктора я собираю решение не медленнее, чем на плюсах, на котором пишу проекты последние лет 5.

      Кстати пробовал golang, прекрасный язык, красивый, куча стандартных и сторонних библиотек, но не получилось на нём разрабатывать быстрее, чем на C/C++.


      1. ECRV
        27.10.2021 14:47
        +2

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

        Условия, которые вы составили в своем сообщении можно перефразировать так: "У нас есть набор готовых проверенных решений и библиотек на Си, нам нужно сделать бизнес-логику и запустить в прод. На каком языке писать будем?"

        Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.

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

        Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел


        1. saterenko
          27.10.2021 15:18

          Конечно, в таком идеальном мире ответ очевиден. Представьте что вы пошли в новую область, в которой еще нет библиотек, в которой нужно выдать отлаженное и покрытое тестами решение как можно быстрее, так оно еще должно быть эффективным по производительности. В этом случае "абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение и все чаще встречаются в жизни.

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

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

          Как показывает мой опыт, для новичков в сфере, к которым вы аппелируете выше ("абстрактные задачи" типа "на голом языке и std" обретают реальное воплощение) фреймоврки значительно затормаживают вхождение в проект, потому как они весьма наворочены и требуют отдельного изучения фреймворка и его осбенностей. Одного языка знать недостаточно.

          С другой стороны, хорошо написанный проект, например nginx, написанный на голом C, с собственной реализацией подавляюшего большинства алгоритмов (в том числе деревьев, hash-таблиц и т.п.), читается легко и непринуждённо... На понимание nginx на С мне потребовалось сильно меньше времени, чем на понимание проекта на PHP на Symfony.

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

          В реальном проекте новочёк облажается на уровне архитектуры, а не на уровне использования тех или иных библиотек. По библиотекам есть документация.

          Я ни в коем случае не намекаю на то что ваш подход не верен. Вы нашли свою нишу, ваш подход в ней эффективен и правилен, бизнес выбирает вас для решения своих задач. Так же как и я, пишу себе код под 8 кБайт оперативки и доволен Си. Автор же говорит о других областях, в которых подход Си устарел

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


          1. ECRV
            27.10.2021 15:55
            +1

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


      1. elektroschwein
        28.10.2021 17:53
        +1

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

        И когда Вася придет в проект, который писал Петя и ещё кто-нибудь, ему кроме понимания самой логики и специфики проекта в довесок ещё придется придется разбираться во всем том, что используют в этом проекте (и это не то, к чему привык он), а когда нужно будет вносить в проекте изменения, придется ещё заново натыкаться на все их подводные камни.

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


        1. saterenko
          29.10.2021 10:08

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

          Проекты бывают разные.


          1. elektroschwein
            29.10.2021 12:20
            +3

            А в данном случае это не важно. Вне зависимости от того, строите вы муравейники или индивидуальный дом, при прокладывании проводки вы все равно будете использовать провода, коробки и автоматы, и все равно следует соблюдать ПУЭ. При проектировании водоснабжения и отопления вы все равно будете использовать трубы, запорную арматуру, отсечные клапана, регуляторы. А вот "кулибиных", порящих в этом деле отсебятину, обычно через N лет матюками припоминают и заказчики, и коллеги. И лучше рисовать чертежи по ГОСТ/ЕСКД/ISO, а не изобретать каждому свои обозначения в документации. Точно так же в мире разработки ПО. Что вы клепаете какую-нибудь простую CRM на базе модного фреймворка, что пишете кастомную систему под специфичные требования заказчика и с хайлоадом - в основе все равно будут те же общеиспользуемые в CS алгоритмы, контейнеры, паттерны, подходы, протоколы. Если только у вас там не совсем что-то оооочень специфическое (например, высокочастотный трейдинг, где все на lock-free построено и хардкорные оптимизации), в чем я сильно сомневаюсь, да и это всё-таки в любом случае редкое явление. Поэтому, если у вас в коде есть утка, желательно чтобы выглядела как утка и крякала как утка.


            1. 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 сплошь отсебятина от "Кулибина".

              Не использование стандартов и знание паттернов делает проект хорошим, работающим, понятным.


              1. 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?


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


                  1. 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 до присвоения ему значения.


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


                      1. eao197
                        30.10.2021 09:46
                        +4

                        Вы задаёте вопросы по стандартной библиотеке для решения задачи генерации случайного uuid

                        Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.

                        Мы здесь даже не будем углубляться в выяснение того, в каком именно стандарте определена uuip_unparse и почему она считается "стандартной библиотекой".

                        Но зато мы можем легко продемонстрировать то, что происходит в реальной жизни: я прихожу в проект, который раньше писался условным Васей Пупкиным, и в первый раз вижу код, вроде показанного вами. И тут у меня возникают вопросы, которые я озвучил. И на которые вы смогли лишь ответить "ищите инфу в интернете". Так ведь это и есть показатель того, что код не так прост в понимании, как может показаться.

                        Вместо использования 4 строк стандартного кода вы предлагаете написать обёртку вокруг стандартной библиотеки, это должно как-то упростить понимание кода???

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

                        Если вы хотели сказать, что в вашем случае uuid получатся одной строкой, а не 4-мя, то в у меня он тоже обёрнут в функцию:

                        К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?


                      1. saterenko
                        30.10.2021 10:09

                        Я задаю вопросы по примеру кода, который вы продемонстрировали. Причем продемонстрировали именно как пример, к которому вопросы не возникнут.

                        Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.

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

                        Вы его, скорее всего, не увидите, вы увидете что-то типа

                        id_ = Utils::makeUUID();

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

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

                        Ну каждый верит во что хочет, да.

                        К вашей функции, помимо всех уже заданных вопросов (про размерность cuuid и последствия ошибок с этой размерностью), можно задать и еще один вопрос: а точно ли мы всегда готовы платить за динамическую аллокацию строки из 37-ми символов?

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

                        Для того места, где это действительно важно, как я писал выше, используется собственная функция генерации случайного uuid, написанная с использованием intrinsic-ов и работающая на 2 порядка быстрее стандартной.


                      1. eao197
                        30.10.2021 10:31
                        +1

                        Этот код демонстрировался как "стандартный", а не как тот, к которому не возникнут вопросы.

                        Обратимся к первоисточнику:

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

                        ...

                        исключительно лучше понимается проффесионалом, чем такой

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

                        Вы его, скорее всего, не увидите, вы увидете что-то типа

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

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

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


  1. 9a75sd
    27.10.2021 12:22

    Любимый момент:

    Использует char*-строки и функции <string.h> вместо std::string и std::string_view. (единственное исключение - строковые константы через constexpr).

    А это хорошо или плохо? String упрощает взаимодействие со строками, но char* и char str[] дает возможность строго определить занимаемое место. Работаю в основном с программированием микроконтроллеров, и неопределенность, которую вносит string, недопустима, и его стараюсь избегать, используя там, где действительно нужно.

    Например, при определении структуры, которая будет передаваться и использоваться в очередях FreeRTOS, string уже не прокатит: недетерминированное поведение строки при передаче через очередь


    1. F0iL Автор
      27.10.2021 12:50
      +1

      Я специально написал в статье:

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

      и пример, когда std::string не подойдет я описал там же в следущем абзаце.


  1. Dovgaluk
    27.10.2021 12:24

    А какая польза от static_cast<int>(number) по сравнению с (int)number?


    1. dvserg
      27.10.2021 12:34
      +2

      Использование static_cast - это проверка допустимости преобразования во время компиляции.


      1. DistortNeo
        27.10.2021 12:42
        -2

        Это понятно. Но какая польза от static_cast<int>(number) по сравнению с (int)number в случае, когда number — это простое выражение с очевидным типом? Очевидный вред от static_cast — это многословность.


        Одно дело, когда мы работаем с шаблонами, и тогда это всё оправдано. И совсем другое — это что-то вроде:


        auto N = (int)ceil(p / 2.0)


      1. Dovgaluk
        27.10.2021 12:57
        +2

        Так там же написано "number". Если бы "непонятно что" или "указатель", то другое дело.


        1. DistortNeo
          27.10.2021 13:02
          +2

          Евангелисты C++ утверждают, что надо использовать static_cast всегда и везде, даже в очевидных случаях.


          При этом, если почитать посты от PVS Studio с разбором кода проектов, то окажется, что ошибки, связанные с неправильным приведением типов, когда мог бы помочь static_cast, встречаются чуть более, чем никогда.


        1. yeputons
          27.10.2021 19:17
          +5

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

          Я вот во втором лагере. Данный пример вижу так:

          Если вы не используете венгерскую нотацию в том или ином виде, то там будет не number, а какое-нибудь другое название.

          Например, есть ли ошибка в следующем коде, если он компилируется и не падает?

          long long timeout_min =
              ((long long)read_config("timeout_sec") + 59) / 60;

          Ответ: а фиг его знает.

          Если там возвращается double (потому что в JSON только вещественные числа) — то, наверное, тут хотели округлить секунды вверх до минут. А вот если там случайно возвращается сишная строка, то вы только что взяли в качестве количества секунд значение указателя. И никаких предупреждений компилятора не получили даже на самом высоком уровне предупреждений.

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

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


          1. lorc
            28.10.2021 00:55
            +1

            с полным отключением статического анализа типов, как в Python

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


            1. 0xd34df00d
              28.10.2021 01:05
              +1

              Интересный способ признаться, что статического анализа в питоне недостаточно. Но, в принципе, это неудивительно — всё же система типов mypy и подобных слаба.


              1. lorc
                28.10.2021 02:09

                Совершенно недостаточно.

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

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


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


                  1. 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).

                    Короче, такие приколы возможны в каком-нибудь Хаскеле, но явно не в питоне. А жаль.


                    1. yeputons
                      01.11.2021 17:40

                      В чистом Haskell, мне кажется, нельзя, там типы нельзя передавать в качестве аргументов функций. Или получаем зависимые типы (dependent types) и какую-нибудь Agda.


                      1. 0xd34df00d
                        01.11.2021 18:29
                        +1

                        Конкретно такое в силу особенностей используемых типов можно только в Idris 2, где есть quantitative type theory и можно паттерн-матчиться на типы.


                      1. lorc
                        01.11.2021 18:56

                        Ну да, я пожалуй неправильно выразился. В Haskell можно использовать какое-то подобие run-time polymorphism, но оно мягко говоря не идиоматично, насколько я понимаю. Поэтому никаких isinsrance() . И та проблема, которую я решал (чуть-чуть более продвинутая десериализация YAML) решается совсем по-другому. Похожий кусок кода там просто в принципе не возник бы.


        1. 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 не разрешит преобразование указателя к целочисленному типу и приведет к ошибке компиляции.
          Но вообще говоря, это костыли, борьба с симптомами болезни, а не с самой болезнью. Приведений типов в коде лучше, по возможности, избегать. ЕМНИП поэтому для операций приведения выбрали такие уродливые длинные названия — чтобы мотивировать разработчиков от этих операций избавляться, переписав код (но конечно ни в коем случае не возвращаться обратно к сишному приведению!). Если же по каким-то причинам приведение типа действительно необходимо, то опять хорошо — уродливый синтаксис поможет не забывать об этом опасном месте в коде. Не лишним будет и написать объяснительную в комментарии, что это за место такое, и почему красиво сделать не получилось.


  1. RomanArzumanyan
    27.10.2021 12:36
    +5

    Почему нигде нет варианта "пишу на Си с классами, потому что в проекте огромная команда, и такой код проще всего развивать и поддерживать"?

    Посмотрите в код Open Source проектов - сплошь и рядом Си с классами. Написали на махровых плюсах - половина контрибьюторов минус.


  1. discipuli
    27.10.2021 12:59

    У меня 3 вопроса:

    1 Чем #define хуже const при объявлении константы? Насколько я помню он же фактически заменяет одну последовательность символов другой на каком-то из начальных этапов компиляции.

    2 А можно поточнее, о каком разделе эмбедеред идёт речь? Точнее на кой нам плюсы даже в 32-битном контроллере для стиралки при том, что на нём можно даже простенькие гуи для сенсорного дисплея писать?

    3 И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си? Ядра ж в конце концов на нём пишут. Речь не идёт о ситуации когда вам проще поставить какой нибудь линукс в свой контроллер и писать уже обычные программы.


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


    1. yeputons
      27.10.2021 19:04

      > И совсем уж нубовский вопрос: какое такое сакральное преимущество даёт C++ системном программировании что бы это нельзя было сделать на си?

      Ни один язык не даёт сакральных преимуществ над другим. Даже если (что не так в случае Си и C++) является строгим надмножеством другого; не всегда больше возможностей — это хорошо.

      А конкретная команды в конкретном проекте делает выбор, исходя из ситуации. Язык, тулчейн, стиль кода, архитектура...


      1. Kotofay
        29.10.2021 23:43

        Сакральное преимущество очень простое: Автоматический вызов деструкторов.


        1. yeputons
          31.10.2021 16:14

          Автоматический вызов деструкторов может приводить к нежелательным эффектам. Например, есть следующие стандартные аргументы (мне, впрочем, автоматический вызов всё равно нравится больше альтернатив):

          1. За } теперь может скрываться сколь угодно сложная операция, работающее произвольное время и потребляющая произвольное количество ресурсов. Обычно, конечно, небольшое, но может и взорваться. Это же, кстати, стандартный аргумент против перегрузки операторов (с которым тоже можно вести холивары).

          2. В частности, если мы пишем какое-нибудь владеющее дерево/список, то при наивной реализации (через unique_ptr и сгенерированные деструкторы) для его уничтожения требуется стек размера с глубину этого дерева. Можно случайно переполнить стек и даже не догадаться: рекурсии-то мы нигде явно не писали, она скрыта.

          3. Деструкторы провоцируют писать логику для закрытия/сброса буферов именно в деструкторах. Создаётся ложное ощущение, что это безопасно, хотя на самом деле ошибки при закрытии (диск отвалился => не удалось файл записать) тут либо полностью игнорируются, либо приводят к падению приложения. Если же, например, есть явный вызов close(), который надо делать абсолютно всегда (либо под угрозой назальных демонов, либо чего-нибудь попроще), то за его наличием следить проще и привычнее.


          1. DistortNeo
            31.10.2021 16:37
            -1

            Верно, я уже наобжигался с RAII, когда писал многопоточный код. Программа уходит в дедлок. А почему? А потому что логика скрыта в деструкторах, отлаживать которые — дело неблагодарное. С тех пор на апологетов RAII смотрю с большим недовольствием, ресурсы чищу вручную, а деструкторы рассматриваю как обработку аварийных сценариев.


            1. Antervis
              31.10.2021 17:31
              +5

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


          1. Kotofay
            31.10.2021 16:54
            +2

            Так разговор шёл именно об "сакральном" отличии С от С++. Почти всё из С++ можно реализовать средствами языка С, но вот это -- к сожалению нет.

            И всё, что вы описали, согласитесь, может произойти и при ручном удалении.

            Преимущество деструкторов в другом -- на каждый return или "}" не нужно вычислять что ты уже освободил а что ещё нет.

            Особенно, если нужно добавить что то в уже существующий код, с множеством управляемых этим кодом ресурсов.


    1. 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 использовать"...


  1. bezboroda
    27.10.2021 13:13
    +2

    Пожалуй malloc/free - самый явный C-прогер детектор ИМХО)


  1. hhba
    27.10.2021 14:36
    +3

    Есть несколько возражений:

    1) Код соответствует стандарту С++, если он компилируется компилятором С++. Шах и мат. Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.

    2) В общем легко отличить программиста на С++ от программиста С даже без этих пунктов. Если есть код, который можно поглядеть (и доказуемо, что его автор перед вами), то там очевидно все будет. Дальше уже могут быть тонкости, например "этот автор как-то плохо разбирается в поведении string_view".

    3) Кажется лучше взять хорошего программиста на С и переучить его на нужный диалект С++, чем упарываться в пуризм.


    1. F0iL Автор
      27.10.2021 15:29
      +2

      Между прочим Бьярне ровно так и говорит - идите нахер, пуристы.

      Что не помешало тому же Бьярне написать C++ Core Guidelines, где он прямым текстом чуть ли не умоляет в современном C++ коде не использовать большинство олдскульных конструкций :)

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

      А я в статье объяснил, почему нам этот вариант не подходит. Спойлер: в проект уже взяли несколько "хороших программистов на C", и теперь нужно укрепить команду как раз теми, кто будет их переучивать в процессе работы и следить, чтобы они дел не наворотили :)


      1. hhba
        28.10.2021 19:09

        в проект уже взяли несколько "хороших программистов на C", и теперь нужно укрепить команду как раз теми, кто будет их переучивать в процессе работы и следить, чтобы они дел не наворотили :)

        Мда, действительно))) Аргумент снимается.


    1. yeputons
      27.10.2021 18:56
      +5

      Код соответствует стандарту С++, если он компилируется компилятором С++.

      Каким из компиляторов? Они, бывает, отличаются и добавляют свои собственные расширения. А ещё поведение отличается между ОС, потому что помимо компилятора есть стандартная библиотека и протекающие абстракции.

      Ill-Formed No Diagnostics Required куда делся?


    1. lorc
      28.10.2021 00:57
      +4

      Код соответствует стандарту С++, если он компилируется компилятором С++

      А что, в стандарте С++ убрали undefined behavior?


  1. Kyushu
    27.10.2021 17:29

    Узнал массу интересного про С++. Спасибо, очень полезно. Видимо, меня уже поздно переучивать и я буду и дальше работать в С++, главным образом, Си-шными методами.


  1. mke61
    27.10.2021 18:03
    -4

    свои пять коп: приплюснутый компайлер считает себя умнее программера, а сишный компайлер считает программера умнее себя. to whom how. ну или ССЗБ.


    1. elektroschwein
      27.10.2021 18:22
      +6

      приплюснутый компайлер считает себя умнее программера

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


  1. AnthonyMikh
    27.10.2021 22:43

    • std::string не подойдет когда вы не можете работать с динамической памятью (хотя, и тут можно придумать что-нибудь интересное с кастомными аллокаторами);

    Так можно просто взять std::pmr::string из стандартной библиотеки. Или это как раз и подразумевается под "кастомными аллокаторами"?


    1. F0iL Автор
      27.10.2021 23:36

      Да, это один из вариантов. Правда, там вроде бы могут быть приколы с fancy pointers, но я с этим не разбирался, это уже вообще отдельная история.


  1. SimSonic
    28.10.2021 05:24

    9/19 "за", так и знал, что я не плюсовик! :)


  1. AdamFull
    28.10.2021 11:36
    +2

    А ещё забыли:
    "Использует #ifndef MY_SOMETHING #define MY_SOMETHING #endif вместо #pragma once",
    "Использует битовые поля вместо std::bitset."
    "Использует сишные библиотеки на прямую без уровня абстракции над ней"
    "В заголовочных файлах куча инклудов, которые можно было в принципе там и не писать (incomplete class)"


    1. DistortNeo
      28.10.2021 13:38
      +2

      А что, #pragma once уже часть стандарта?
      Ах да, ещё модули есть.


    1. 0x3f00
      28.10.2021 13:48

      Увы, имею опыт выстреливания в ногу и с #ifndef HEADER_H..., и с #pragma once.


  1. ldss
    29.10.2021 18:12
    -2

    Использует ручное управление памятью с new и delete, вместо RAII и умных указателей;

    Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки

    Для деструкторов забывает virtual :)

    А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу


    1. Aldrog
      29.10.2021 18:46
      +3

      Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки

      Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.


      А у вас постоянно встречается ромбовидное наследование, что ли? Это же фу-фу

      Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.


      1. ldss
        29.10.2021 21:16

        Однако, в современных плюсах их использование есть моветон, а сишникам наверняка проще привыкнуть писать new/delete вместо malloc/free, чем освоить концепцию RAII.

        new/delete - это чисто с++ концепции (вызов конструктора/деструктора при аллокации/очистке памяти), к С не имеют никакого отношения

        Причём здесь ромбовидное наследование? Отсутствие виртуального деструктора в любом интерфейсном (абстрактном) классе — это выстрел себе в ногу.

        [вопрос снимается, ниже ответили]


    1. F0iL Автор
      29.10.2021 19:00
      +2

      Я конечно извиняюсь, но new&delete с самого начала были именно что с++ фишки

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

      А у вас постоянно встречается ромбовидное наследование, что ли?

      А причем здесь ромбовидное наследование вообще? Виртуальный деструктор нужен даже при простом прямом наследовании, когда вы на объект дочернего типа имеете ссылку или указатель типа базового класса, то без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.


      1. ldss
        29.10.2021 21:26

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

        я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях

        Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп

        без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.

        ясно

        ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки


        1. F0iL Автор
          30.10.2021 00:39

          Могу много рассказать про преимущества ручного управления памятью в частных случаях

          Никто не спорит, что есть частные случаи, когда такое имеет смысл и оправданно. А вот когда причин особых нет...

          я уже давно живу в мире, где есть garbage collectors.

          В мире C++ они хоть и не часть языка и стандартной библиотеки, но всё-таки есть. Например, Oilpan GC активно используется в Blink (движок рендеринга браузера Chromium и Android WebView). А ещё есть Boehm.

          Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп

          Ну да, это незнание возможностей стандартной библиотеки и незнание общепринятых практик написания кода на современном стандарте языка.

          К принципам ООП, впрочем, умные указатели отношения имеют мало (разве что в плане самих понятий "конструктор" и "деструктор", на которые они полагаются). Умные указатели можно встретить даже там где вообще нет ООП, например в проектах на чисто процедурном Си, я лично такое видел, как одни наркоманы реализовали что-то подобное с помощью макросов и атрибута cleanup в GCC/Clang. (Upd.: я тут погуглил и даже нашел библиотечку для такого).


          1. ldss
            01.11.2021 18:09

            К принципам ООП, впрочем, умные указатели отношения имеют мало (разве что в плане самих понятий "конструктор" и "деструктор", на которые они полагаются). Умные указатели можно встретить даже там где вообще нет ООП, например в проектах на чисто процедурном Си

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

            Но это ж несерьезно. Умения программиста они не совсем о знании фреймворков


            1. F0iL Автор
              01.11.2021 18:39

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


        1. Aldrog
          30.10.2021 00:44
          +1

          я уже давно живу в мире, где есть garbage collectors. Могу много рассказать про преимущества ручного управления памятью в частных случаях

          GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.


          Имхо, не-пользование unique_ptr это скорее про незнание отдельных аспектов стандартной библиотеки, а не про понимание принципов ооп

          Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.


          без virtual при delete вызовется только деструктор парента, но не производного, и вы можете получить утечку ресурсов.

          Здесь на самом деле всё ещё хуже, такой delete вообще является UB, и, например, в моей практике приводил к memory corruption, после чего любая аллокация могла с некоторым шансом вызвать дедлок.


          1. ldss
            01.11.2021 18:16

            GC и smart pointers — очень разные концепции, и последние покрывают большинство сценариев, где GC уступает ручному управлению.

            идею-то одна - автоматическое управление памятью

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

            Это действительно не имеет никакого отношения к ООП. Это про RAII — гораздо более важную и центральную особенность C++. Не понимать и не использовать RAII == не знать плюсы.

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


            1. F0iL Автор
              01.11.2021 18:43

              это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)

              Есть много случаев, когда GC неуместен вообще, например когда у нас real-time или ограничены ресурсы. Просто из-за того, что многие GC явно делают stop-the-world, а если даже и не делают, то накладные расходы на его работу все равно явно ненулевые.

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

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


              1. ldss
                01.11.2021 19:20

                Есть много случаев, когда GC неуместен вообще, например когда у нас real-time или ограничены ресурсы.

                насколько мне известно, в таких случаях будет использоваться скорее с, а не плюсы:))

                Просто из-за того, что многие GC явно делают stop-the-world, а если даже и не делают, то накладные расходы на его работу все равно явно ненулевые.

                конечно

                как и на все фишечки std. Это стандартный трейдоф удобство вс перформанс; но чаще-то всего железо позволяет

                помню, один товарищ (игродел) рассказывал, что они даже виртуальные функции стараются не использовать в борьбе за fps - переход по vtable ест пару тактов:) Но это давно еще было

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

                он адски сложный. Сколько там стандарт уже, полторы тыщи страниц? Честно, не вижу особого смысла его ни вспоминать, ни учить.

                При этом, взять хотя бы вот это обсуждение - тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык. Такая вещь в себе этот ваш с++
                Причем, мне сейчас за этот камент еще и минусов насуют, очень нетерпимая коммьюнити:))


                1. F0iL Автор
                  01.11.2021 19:38
                  +2

                  насколько мне известно, в таких случаях будет использоваться скорее с, а не плюсы:))

                  Я уже с десяток лет с подобным работаю. Именно на плюсах. И таких проектов все больше и больше.

                  как и на все фишечки std

                  Слово "все" здесь совершенно лишнее. В современных плюсах и в std есть немало фишечек с нулевым оверхедом. Ну и сравнивать накладные расходы на вызов виртуальной функции с очередной итерацией работы сборщика мусора - ну такое, там порядки совершенно разные, как и детерминированность временных затрат.

                  он адски сложный. Честно, не вижу особого смысла его ни вспоминать, ни учить.

                  Тут каждому своё, вас никто и не заставляет. Я, когда начинал, видел  смысл, и до сих пор не жалею.

                  Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык.

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

                  При этом, взять хотя бы вот это обсуждение - тут 80% дискуссии о том, как правильно использовать фишки языка.

                  В каждом языке есть свои хорошие практики и свои понятия чистого и идиоматического кода. Тут нюанс в том, что придти, например, к разработчикам на Java или на C# и начать писать на их языке как на Фортране или на чистом Си не получится, потому что большую часть подобного "олдскула" вам язык сделать так просто не даст, а за другую половину вам шарписты и джависты больно напихают на первом же ревью. А вот на C++ по ряду причин такое сделать получится легко, и как показывает практика, очень многие пытаются так делать. Об этом и статья.


                  1. ldss
                    01.11.2021 20:24

                    Я уже с десяток лет с подобным работаю. Именно на плюсах. И таких проектов все больше и больше.

                    а на Go или там Rust народ не хочет переходить? Не помню, какой там из них в нативный код компилится

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

                    это понятно, я все еще помню жаркие дискуссии с vs c++ в начале этак 2000х. "Веревка достаточной длины, чтоб выстрелить себе в ногу", а как же:)
                    Но это, имхо, не достоинство прикладного языка


                    1. F0iL Автор
                      01.11.2021 21:00

                      Не помню, какой там из них в нативный код компилится

                      Оба в нативный код, но Go с собой рантайм таскает не самый легкий.

                      а на Go или там Rust народ не хочет переходить? 

                      К Rust присматриваются по-тихоньку.


                      1. ldss
                        01.11.2021 21:36

                        Пора, пора!
                        Еще kotlin native есть, к слову
                        У меня был как-то проект, полуembedded (десктоп клиент и управляющая программа на железе), так там на железе натурально комп с winnt стоял, мы туда даже студию вкорячили, для отладки:)) Там с go наверное не было бы проблем, но его тогда и не было еще


                1. Aldrog
                  01.11.2021 19:54
                  +2

                  При этом, взять хотя бы вот это обсуждение — тут 80% дискуссии о том, как правильно использовать фишки языка. Не как правильно определить бизнес логику/архитектуру/алгоритмы, а как правилно использовать язык.

                  А что ещё вы ожидали от комментариев к статье, поднимающей вопрос идеоматичного кода на C++?


                  1. ldss
                    01.11.2021 20:59

                    так статья, по сути, не об умении программировать, а о знании некоторых специфических (пусть и широко распространенных) особенностей фреймворка (std)

                    Мне такое странно, фреймворк и фреймворк, нормальный прогер основные положения выучит за пару месяцев, это не рокет саенс. А тут прям такое внимание


                    1. Aldrog
                      01.11.2021 21:46
                      +2

                      Статья о сравнении концепций, которые принято применять в C и C++. Да, часть из них реализуется с помощью абстракций, которые не входят в сам язык, но 1) от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL 2) другие фреймворки в большинстве своём всё равно либо опираются на STL, либо предлагают аналогичные абстракции — использовать сишные строки или вручную управлять памятью вы наверняка не будете, независимо от фреймворка.


                      1. ldss
                        02.11.2021 17:47

                        от программиста на C++ обычно ожидается хотя бы поверхностное знакомство с STL

                        по статье это не заметно:)


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


            1. Antervis
              01.11.2021 19:17
              +2

              идею-то одна - автоматическое управление памятью

              в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже

              Да и в тех сценариях, где нужно ручное управление, умные указатели будут такими же (если не более) неуклюжими, как и gc

              Будем откровенны, смарт поинтеры - это все таки костыль в сравнении в нормальным gc (вот который в дотнете, например)

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

              А ручное управление памятью в языке с GC это костыль для бекдора в другом костыле...

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

              а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект. RAII позволяет делать герметичные объекты, которые при уничтожении освобождают все ресурсы. Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.

              ужас какой, как хорошо, что я на плюсах уже почти и не пишу; забыл уже все эти заморочки

              учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...


              1. ldss
                01.11.2021 19:45

                в случае с умными указателями - не только памятью, а объектами в общем, пояснение ниже

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

                Во-вторых, сам по себе GC не дает абсолютно ничего в сравнении с умными указателями, кроме некоторого снижения когнитивной нагрузки

                он дает чудовищное снижение этой самой нагрузки

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

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

                это не к языку, это к CLR/virtual machine

                Что касается рантайма - разница там не сказать чтоб значительная, к тому же выделение памяти в managed heap сильно быстрее, чем в нативной, в силу отсутствия дефрагментации; да и современные gc нонеча работают уже совсем не так как давеча

                а что такое ООП по вашему? Из трех "краугольных камней" ООП основополагающей является именно инкапсуляция. Мы объединяем логику, данные и ресурсы, в некоторую единую абстракцию - объект

                одно из самых странных обьяснений ООП что я слышал, если честно

                противоречит хотя бы тому же single responsibility

                Иначе могут получаться "протекающие" абстракции - объект уничтожен, а ассоциированный с ним ресурс - нет. Что собственно и происходит в языках с GC.

                не, не происходит:) Ну разве что с кастомными плохо спроектированными обьектами, использующими unmanaged resources

                учитывая степень вашего понимания базовых концепций программирования хорошо конечно, что вы не пишете на с++...

                каким образом понимание программирования на с++ связано с пониманием программирования вообще? Имхо, тут скорее наоборот, знание с++ требует столько умственных усилий, что на собственно программирование их и не остается


                1. F0iL Автор
                  01.11.2021 19:50

                  Но суть все равно та же - автоматически освобождать ресурсы, когда они не нужны

                  В том же Шарпе это будет не "когда они не нужны", а "когда они не нужны и до них наконец-то доберется сборщик мусора". Поэтому в языке пришлось изобретать костыли с Dispose() и using.


                  1. ldss
                    01.11.2021 20:17

                    В том же Шарпе это будет не "когда они не нужны", а "когда они не нужны и до них наконец-то доберется сборщик мусора". Поэтому в языке пришлось изобретать костыли с using.

                    ну что вы, давным давно уже не так

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

                    что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
                    IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейс
                    такой аналог try/finally


                    1. F0iL Автор
                      01.11.2021 21:11

                      при этом, все обьекты он проверять на удаление не будет.

                      ну да, там поколения еще есть, если я правильно помню.

                      что до using - это не про чистку памяти, это про гарантированный вызов некоей закрывающей функции, например, чтоб файл закрыть, или там курсор по базе
                      IDisposable никоим образом не гарантирует освобождение ресурсов, это просто интерфейс

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


                      1. ldss
                        01.11.2021 21:58

                        ну да, там поколения еще есть, если я правильно помню.

                        там много чего есть:)

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

                        а если мне закрыть надо, а ресурс освобождать нет?:) Они ж довольно примитивные, unique_ptr эти. Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?

                        Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?


                      1. Aldrog
                        02.11.2021 11:01
                        +1

                        а если мне закрыть надо, а ресурс освобождать нет?

                        То вероятно это должны быть два разных уровня абстракции.


                        Никто ж не строит графов обьектов и не проверяет ссылки на них; вызвал кто reset, и все. А если на указатель внутрях еще один unique ссылается?

                        unique_ptr называется unique не просто так, два unique_ptr не могут ссылаться на один объект. Для таких случаев существует shared_ptr, который ведёт счётчик ссылок и удаляет объект только вместе с последней ссылкой.


                        Ну и вроде все равно надо явно вызывать что-то типа unique_ptr.reset(nullptr), нет?

                        Нет, обычно полагаются на вызов деструктора при выходе из скоупа. Вызов unique_ptr::reset() практически полностью аналогичен delete и существует для тех ситуаций, когда вам всё-таки необходимо ручное управление ресурсами.


                      1. ldss
                        02.11.2021 18:19

                        unique_ptr называется unique не просто так, два unique_ptr не могут ссылаться на один объект.

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

                        Нет, обычно полагаются на вызов деструктора при выходе из скоупа

                        Понятно

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

                        О чем я и говорю, очень примитивная обертка, не гарантирующая, по сути, ни от чего


                      1. F0iL Автор
                        02.11.2021 19:03
                        +2

                        в документации ничего про это не сказано

                        Прямым текстом сказано вообще-то:

                        Более того, если вы пользуетесь только умными указателями и не достаете из них / не пихаете в них сырые, то у вас в принципе не может возникнуть ситуация, когда на один объект указывают сразу несколько unique_ptr'ов.

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

                        Зачем? Оно гарантированно автоматически немедленно удалится когда вы выйдете из скоупа, причем вне зависимости от того, как именно выйдете. Объявили объект внутри функции - он удалиться как только произойдет выход из функции. Объявили объект внутри блока {} - он удалится когда закончится этот блок. Объявили объект внутри другого объекта - он удалится когда удалится родительский объект.
                        В конце концов, если вам непонятно зачем, но хочется сделать это еще раньше, никто не запрещает вручную сделать release(), и "к черти чему" это не приведет - точно так же вызовется деструктор объекта, как и во всех других случаях, никаких неожиданностей.


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


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


                      1. ldss
                        04.11.2021 18:46

                        похоже, мы всю дорогу о разном говорили.
                        unique_ptr тут вообще сбоку, говорить надо было про auto_ptr


                      1. F0iL Автор
                        04.11.2021 19:34
                        +1

                        auto_ptr уже 10 лет как deprecated и уже 4 года как удален из стандарта языка.


                      1. ldss
                        05.11.2021 20:13

                        угу, вижу

                        Суть моих филиппик очень простая - сам по себе язык и окружение не гарантируют ни от чего; как и 20 лет назад, надо самому за всем внимательно следить.


                      1. Aldrog
                        02.11.2021 19:03
                        +2

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

                        У него просто нет копирующего конструктора / присваивания, только перемещающие.
                        Выстрелить себе в ногу можно разве что хитро извернувшись в стиле unique_ptr{other_ptr.get()}.


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

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


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

                        а так?


                      1. Antervis
                        04.11.2021 18:55
                        +1

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

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

                        Что до здравого смысла - он ведь не дает вам например выпрыгнуть из окна, верно?

                        а так?

                        вы видите тут вызов копирующего конструктора?


                      1. ldss
                        05.11.2021 20:00

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

                        я о том, что в силу особенностей языка использование unique_ptr на спасет ни от чего, выстрелить в ногу можно точно так же, как и 20 лет назад
                        просто тогда б-м прошаренный народ писал свои обертки и использовал всякие creator patterns
                        а вот наличие gc от такого вполне так себе гарантируeт


                      1. F0iL Автор
                        05.11.2021 21:01

                        в силу особенностей языка использование unique_ptr на спасет ни от чего

                        Использование unique_ptr защищает об большинства ошибок по невнимательности, но не защитит, если вы упорно намеренно и осознанно пытаетесь выстрелить себе в голову.


                      1. ldss
                        05.11.2021 21:10

                        Использование unique_ptr защищает об большинства ошибок по невнимательности, но не защитит, если вы упорно намеренно и осознанно пытаетесь выстрелить себе в голову.

                        или использую его неправильно, от чего язык не защищает никак

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


                      1. DistortNeo
                        04.11.2021 18:56
                        +1

                        А что вы этим кодом хотели показать? Что у типа int * есть конструктор копирования? Ну так не используйте указатели, и будет вам счастье.


                        Нормальный человек же напишет:


                        auto test = std::make_unique<int>(10);

                        После чего закономерно словит ошибку компиляции.


                      1. ldss
                        05.11.2021 20:02

                        не словит


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

                        Никаких проблем, кстати, со вторым кодом не будет. Но это просто дичь.


                      1. ldss
                        05.11.2021 21:05

                        Конечно же словит. Нельзя привести std::unique_ptr к int*.

                        я просто не понял, что вы имели в виду


                      1. 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++ есть множество способов выстрелить себе в ногу, но не надо приплетать к ним случаи, возможные только в искуственных примерах.


                      1. ldss
                        05.11.2021 20:06

                        А само наличие new совершенно очевидно указывает на проблемное место, и ни один плюсовик, знакомый с хорошими практиками, такую ошибку никогда не допустит, и через кодревью не пропустит.

                        ну т.е. в языке есть задокументированная в стандарте конструкция, пользоваться которой нельзя:) Понятно:)

                        Скорее всего вы имели в виду

                        да я уже посмотрел внутрь make_unique, вижу, что он указатель на переданное value создает
                        А как быть с созданием картинок, например, в цикле? Создал-обработал-автоудалил? Мне штож, в стеке их создавать?

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

                        как-то, говорю ж, с 98 года не сильно все изменилось


                      1. 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 - это вообще почти что два разных языка. И второй гораздо лучше.


                      1. ldss
                        05.11.2021 21:33

                        Можно, если вам это действительно нужно и вы понимаете, что и зачем вы делаете.

                        если судить по исходной статье и обсуждению, такого знающего просто на работу не возьмут:))

                        А в чем сложность? Сделали make_unique внутри цикла, он автоматически удалит созданную картинку при завершении итерации.

                        вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?

                        Изменилось все настолько сильно, что я бы сказал, что C++98 и C++17 - это вообще почти что два разных языка

                        эт не язык изменился, это стандартная библиотека изменилась


                      1. Aldrog
                        06.11.2021 00:14
                        +2

                        вопрос, что мне передавать в пареметр make_unique? Картинку (например, в полгига), созданную на стеке?

                        Может, наконец, прочитаете документацию?
                        make_unique переадресует аргументы в конструктор типа, его суть ровно в том, что вам не нужно сначала создавать объект, а потом помещать в unique_ptr, как приходилось делать до c++14.


                        эт не язык изменился, это стандартная библиотека изменилась

                        Обсуждаемые нововведения были бы малополезны без появившегося в c++11 move semantics.


                      1. 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, и т.д.)


                1. 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 никоим образом не гарантирует освобождение ресурсов, это просто интерфейс

                  собственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов?


                  1. ldss
                    01.11.2021 22:22

                    файловые дескрипторы, сокеты, мьютексы в конце концов. RAII дает куда больше чем простое освобождение памяти.

                    да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец

                    вы сильно недооцениваете моё понимание плюсов и сильно переоцениваете моё понимание джавы/шарпа.

                    ну, вот у меня был такой опыт. Очень освежающий!! Когда через пару дней активной разработки мы вдруг сообразили - мама дорогая, это ж не дотнет, надо ж самим обьекты прибирать! И началось..

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

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

                    Подробнее можете ознакомиться в этом докладе

                    в докладе говорится, что гц добавляет оверхед - с этим-то я вообще не спорил; я говорил, что а) на современном железе на него чаще всего пофиг, б) помещение обьекта в управляемую память - быстрее

                    ну и докладчик не очень знает про современные стратегии сборки мусора:)

                    Парадигма ООП основана на инкапсуляции

                    нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО

                    действительно, какие проблемы выжрать 300 гигов памяти на серваке, а потом потратить десяток секунд на её очистку, пока все остальные приложения ждут...

                    ну так если вы настроили вирт. машину так, что ей можно 300 гиг памяти, кто ж виноват

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

                    Иными словами, пофиг, кто сколько сожрал памяти, если ее хватает

                    собственно в этом и проблема. Вы разве никогда не сталкивались с проблемами типа заканчивающихся файловых дескрипторов

                    нет, конечно, потому что я гарантированно закрываю открытые файлы

                    IDisposable - это просто интерфейс, позволяющий имплементировавшему его классу быть использованным в конструкции using. Как имплементацию напишете, так и будет - ровно как и в случае с деструктором вашего файлового обьекта


                    1. Antervis
                      01.11.2021 23:31
                      +2

                      да обычный менеджмент ресурсов. Мы еще в 2000х писали auto critical sections, ничего тут особенного нет, раз открыл - закрой; а если еще и будешь делать это через спец-обьект на стеке, так и вообще молодец

                      это и называется "протекающие абстракции"

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

                      это в теории там просто непрерывный последовательно заполняющийся кусок памяти. На практике это конечно же не работает (фрагментация уводит потребление памяти в космос) и приходится реализовывать полноценные аллокаторы, распределяющие память внутри этой managed heap.

                      б) помещение обьекта в управляемую память - быстрее

                      по сути это получается полноценная деаллокация. Грубо говоря, любой аллокатор, который используется внутри managed heap, можно просто подключить к плюсам и, если это выгодно, получить профит, безо всяких затрат на GC. Просто обычно это не делают потому, что это не выгодно.

                      ну и докладчик не очень знает про современные стратегии сборки мусора:)

                      а это не важно. Сколько ни оптимизируй/усложняй стратегию сборки мусора, отрицательной или даже нулевой её стоимость не станет.

                      нууууу... Об этом до сих пор спорят, на чем же она основана. А один из основателей так и вообще сказал, что он имел в виду скорее event driven, а не вот ЭТО

                      ООП по версии Алана Кая конечно больше похож на то, что нынче называют "акторами", но инкапсуляция в его парадигме всё так же фундаментально необходима

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

                      Во-первых, вы предполагаете что разработка на языках с GC быстрее чем на тех же плюсах. Начиная с с++11 это как минимум не очевидно и не безусловно. Во-вторых, не всегда можно пренебречь стоимостью железа и не всегда есть его стократный запас. При сравнимой стоимости разработки ценник в $1m за облачную инфраструктуру покажется куда привлекательнее $5m.


                      1. 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 него не определяет, это просто обертка


                      1. 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(), а память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения


                      1. DistortNeo
                        02.11.2021 21:28

                        ну оверхед джавы/шарпа относительно плюсов по CPU порядка 2-4 раз

                        Я недавно делал замеры скорости работы алгоритмов обработки изображений на C# и C++: C++ оказывался в среднем всего на 20% быстрее, чем C#. Разница в разы была, наверное, лет 10 назад.


                      1. Kotofay
                        03.11.2021 17:22

                        Недавно столкнулся стем что на Java LU-разложение быстрее чем на С++, на несколько процентов. Исходные коды идентичны.

                        Правда использовался GraalVM vs Visual C++.


                      1. F0iL Автор
                        03.11.2021 17:49

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


                      1. Kotofay
                        03.11.2021 18:14

                        В том то и дело что использовался CLang и максимально возможные оптимизации, + AVX.

                        Честно говоря я был немного в шоке от GraalVM.


                      1. ldss
                        04.11.2021 18:38

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

                        ну так в реалиях-то как раз эта сборка происходит редко, да и сбор в первой генерации быстрый. При этом не будет out of memory из-за дефрагментации памяти

                        какой блин "дьявол" то? У вас есть дополнительные расходы на механизм, который своим существованием не может сэкономить ресурсы даже теоретически.

                        теоретически - да
                        А на практике - очень даже. Потому что сбор мусора происходит редко

                        то, что язык сложнее, не значит, что писать на нём тоже сложнее.

                        именно это оно и значит:))

                        Взять например питон - на дистанции в 1-10 kloc писать на нём проще чем на чем угодно другом; писать на питоне проект на 100+ kloc - самоубийство

                        ну так давайте не питон возьмем, а сишарп, или там, свят-свят, жаву

                        вы посмотрите средние ЗП плюсовиков и джавистов, удивитесь

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

                        Нелогично было бы если бы приходилось для каждого объекта unique_ptr писать финализатор, который вызовет ~T(),

                        ну в с++ наверное
                        а в сишарпе это просто деструктор

                        а память освобождалась хитрым и тормозным неявным способом, останавливающим все потоки исполнения

                        финализация идет в отдельном потоке, о чем вы?

                        И еще раз замечу - вы чересчур переоцениваете необходимость своевременной чистки памяти. В частных случаях - да, в целом нет


                      1. 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 часа. Вся эта память могла бы использоваться на что-то полезное, например на браузер. Вот это частный случай, или всё-таки общий?


                    1. 0xd34df00d
                      02.11.2021 22:47
                      +1

                      Иными словами, пофиг, кто сколько сожрал памяти, если ее хватает

                      На самом деле проблема с GC в том, что чем меньше дополнительной памяти ему доступно, тем медленнее он работает.


                      У меня в практике есть проекты, которые требовали сотню-другую гигабайт памяти (матрицы с недобигдатой вертали по-всякому), и запускались на машинах с 384-512 гигабайтами ОЗУ. GC туда бы уже не влезло.


                      1. ldss
                        04.11.2021 18:48

                        ну, про такие примеры не знаю, не сталкивался, потому спорить не буду


                  1. 0xd34df00d
                    02.11.2021 22:44
                    +1

                    RAII дает куда больше чем простое освобождение памяти.

                    RAII не даёт ничего, кроме, по факту, «выполни вот этот код при выходе из скоупа». В частности, нет никаких гарантий, что на ресурс, который подчистит ваш RAII-класс, не останется никаких ссылок (вроде сырого указателя или ссылки на объект под умным указателем, native_handle у std::thread и прочих подобных, етц).


                    Такие гарантии (вместе с предсказуемым потреблением ресурсов) дают только типы.


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

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


                    1. Antervis
                      02.11.2021 23:09
                      +1

                      RAII не даёт ничего, кроме, по факту, «выполни вот этот код при выходе из скоупа»

                      Во-первых, этого более чем достаточно противопоставляя "когда-нибудь GC подчистит память, остальное будь добр руками". Во-вторых, не обязательно "при выходе из скоупа". В третьих, вы опять придумываете гипотетические проблемы.

                      Такие гарантии (вместе с предсказуемым потреблением ресурсов) дают только типы.

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

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

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


                      1. 0xd34df00d
                        02.11.2021 23:23
                        +1

                        Во-первых, этого более чем достаточно противопоставляя "когда-нибудь GC подчистит память, остальное будь добр руками".

                        Мы обсуждаем производительность или корректность?


                        Во-вторых, не обязательно "при выходе из скоупа".

                        Ну а когда RAII ещё срабатывает?


                        Да, я знаю, что умный указатель можно куда-то мувнуть, но это уже детали.


                        В третьих, вы опять придумываете гипотетические проблемы.

                        C++ приучил :(


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

                        Не понял, почему это?


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


                        Плюс, ФП ≠ иммутабельность. В этих наших хаскелях вполне есть локальная мутабельность в той же монаде ST (и типы позволяют её отслеживать и убеждаться, что мутабельность именно что локальная).


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


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

                        Ничему не мешает, просто показывает, что SRP таки нарушается.


                      1. Antervis
                        03.11.2021 00:43
                        +2

                        Мы обсуждаем производительность или корректность?

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

                        Обсуждается производительность и универсальность соответствующих инструментов. Моя позиция - GC не может быть в общем случае быстрее и ограничивается лишь одним видом ресурсов - памятью, оставляя остальные на ручное удаление программистом.

                        Ну а когда RAII ещё срабатывает?

                        когда мы удаляем объект любым способом. Например, я же не выхожу из скоупа когда делаю optional<T>::operator=(nullopt), но эта операция удаляет вложенный объект при его наличии, вызывая его деструктор.

                        C++ приучил :(

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

                        Не понял, почему это?

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

                        Плюс, ФП ≠ иммутабельность. В этих наших хаскелях вполне есть локальная мутабельность ...

                        мутабельность в ФП это бекдор, противоречащий парадигме. Собственно, поэтому и имеет смысл её локально ограничивать.

                        Ничему не мешает, просто показывает, что SRP таки нарушается.

                        можете показать где конкретно нарушается принцип единственной ответственности? Вот хоть убей не вижу.


                      1. 0xd34df00d
                        03.11.2021 02:42
                        +1

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

                        Это был риторический вопрос.


                        Моя позиция — GC не может быть в общем случае быстрее и ограничивается лишь одним видом ресурсов — памятью, оставляя остальные на ручное удаление программистом.

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


                        когда мы удаляем объект любым способом. Например, я же не выхожу из скоупа когда делаю std::optional<T>::operator=, но эта операция удаляет вложенный объект при его наличии, вызывая его деструктор.

                        Часто встречаете такой код для T ~ std::unique_ptr<T'> или myDbConnection?


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

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


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

                        Ну а вот в ФП с точки зрения перформанса объекты принимаются по ссылке, а не по значению (с точки зрения семантики это неразличимо в ФП, понятное дело). Плюс, что куда важнее, я не очень понимаю, как по значению передать, например, файл или соединение с БД. Что это вообще значит?


                        мутабельность в ФП это бекдор, противоречащий парадигме. Собственно, поэтому и имеет смысл её локально ограничивать.

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


                        можете показать где конкретно нарушается принцип единственной ответственности? Вот хоть убей не вижу.

                        Там, где объект начинает отвечать и за абстракцию над fd, и за конкретный специальный случай обычных файлов на диске. Это разные вещи.


                      1. 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 не нарушится ни в одном из случаев, это всего лишь разные уровни абстракции.


                      1. 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, что про них?

                        1. Как часто они там встречаются?
                        2. У вас мир C#/Java ограничивается, что ли? Должно быть, это очень грустный мир.

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

                        Вы можете как-то сразу определиться со своими аргументами? То у вас все объекты передаются по значению, то это теперь неважно.


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

                        SRP вообще имеет смысл-то так? А то ведь для любого описания функциональности есть уровень абстракции, где это единый юнит.


                      1. 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 это просто свод указаний, а не жестких законов.


                      1. 0xd34df00d
                        04.11.2021 04:01
                        +2

                        Во-первых, это вопрос опыта разработки на конкретном языке

                        Ну вот моих 18 лет опыта, очевидно, недостаточно. Практика дырок в других приложениях показывает, что там тоже недостаточно.


                        во-вторых, логика и реализация продумываются на разных этапах?

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


                        не часто, но важно ведь не это. Важно то, что деструктор сработает при любом удалении объекта, а не только при неявном выходе из скоупа (чего в общем-то можно добиться и всякими defer'ами, пусть и многословнее).

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


                        как часто null deference встречается в современных плюсах? В тех, где сырые указатели в принципе используются сильно реже ссылок и умных.

                        А зачем вы спорите с соломенными чучелами? Давайте лучше сравним количество висячих ссылок, выходов за границы и так далее.


                        это самые яркие примеры языков общего назначения с GC, которые в остальном похожи на плюсы.

                        Зачем похожесть на плюсы? Императивно-нулловая философия накладывает свои ограничения.


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

                        А если я его после этого сохранил как поле объекта?


                        А во-вторых, забываете что принципы SOLID это просто свод указаний, а не жестких законов.

                        Я не то что забываю, я изначально во всех этих солидах не шарю, там сложно чё-т слишком.


                      1. Antervis
                        04.11.2021 11:56
                        +1

                        Ну вот моих 18 лет опыта, очевидно, недостаточно. Практика дырок в других приложениях показывает, что там тоже недостаточно.

                        опыт коммерческой разработки с 12 лет то?

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

                        проблема лайфтаймов ортогональна GC и решается без него (как в том же расте)

                        А зачем вы спорите с соломенными чучелами? Давайте лучше сравним количество висячих ссылок, выходов за границы и так далее.

                        хотите сказать в managed языках не бывает выхода за границы?

                        Зачем похожесть на плюсы? Императивно-нулловая философия накладывает свои ограничения.

                        так вся дискуссия про то, насколько полезен GC. Соответственно желательно сравнивать языки, которые максимально близки во всем, кроме наличия/отсутствия GC.

                        А если я его после этого сохранил как поле объекта?

                        вариантов уйма же, самый простой - копирование.

                        Я не то что забываю, я изначально во всех этих солидах не шарю, там сложно чё-т слишком.

                        вы постоянно находите простейшие вещи невероятно сложными... И наоборот


                      1. 0xd34df00d
                        04.11.2021 20:20
                        +2

                        опыт коммерческой разработки с 12 лет то?

                        Коммерческой — где-то с 16-17. Но не обязательно получать деньги, чтобы продуктивно наступать на грабли.


                        проблема лайфтаймов ортогональна GC и решается без него (как в том же расте)

                        Ну наконец-то вы сами начали подходить к тому, о чём я говорил изначально — о типах, то бишь.


                        хотите сказать в managed языках не бывает выхода за границы?

                        Хочу сказать, что в managed-языках без unsafe выход за границы отлавливается и не является UB.


                        так вся дискуссия про то, насколько полезен GC. Соответственно желательно сравнивать языки, которые максимально близки во всем, кроме наличия/отсутствия GC.

                        Нет. Имеет смысл рассматривать языки, которые без GC реализуются тяжко (функциональщину там всякую).


                        вариантов уйма же, самый простой — копирование.

                        Ну ещё раз, скопируйте мне соединение с БД.


                      1. Antervis
                        04.11.2021 21:27

                        Ну наконец-то вы сами начали подходить к тому, о чём я говорил изначально — о типах, то бишь.

                        в дискуссии о полезности GC?

                        Хочу сказать, что в managed-языках без unsafe выход за границы отлавливается и не является UB.

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

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

                        Нет. Имеет смысл рассматривать языки, которые без GC реализуются тяжко (функциональщину там всякую).

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

                        Ну ещё раз, скопируйте мне соединение с БД.

                        shared_ptr<DBConnection> conn(otherConn); ?


                      1. 0xd34df00d
                        04.11.2021 22:13
                        +2

                        в дискуссии о полезности GC?

                        В дискуссии о методах управления ресурсами.


                        Это всё еще ошибка, она всё еще допущена, код всё еще работает некорректно.

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


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

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


                        shared_ptr<DBConnection> conn(otherConn);

                        Очень по значению, ага.


                        От того, что вы указатель завернули в класс и класс передаёте по значению, вся программа в «только по значению» не превратилась.


                      1. Antervis
                        04.11.2021 23:06

                        В дискуссии о методах управления ресурсами.

                        GC не является "методом управления ресурсами", а только лишь памятью.

                        Нет, код не работает некорректно.

                        "Код не работает корректно", whatever. Вы пытаетесь продать исключение при выходе за границу так, словно оно предотвратило ошибку. Однако надежность системы не выросла ни на йоту.

                        Код вообще не работает, потому что его выполнение прервалось экзепшоном.

                        "Прервалось экзепшном" != "не работает". Максимально вероятно код будет продолжать работать каким-то не запланированным способом. Так же как и в плюсах. Но у вас почему-то в одном языке в выходе за границу массива будет виноват программист, а в другом языке - сам ЯП. Где в вашем сознании пролегает та самая эфемерная граница, их разделяющая? Возможно это банальная предвзятость?

                        Очень по значению, ага.

                        От того, что вы указатель завернули в класс и класс передаёте по значению, вся программа в «только по значению» не превратилась.

                        Я не понимаю чего вы от меня добиваетесь. Я ж говорю - передавать объекты в функции в абстрактном чисто функциональном языке можно практически как угодно. Вроде логично, что любой иммутабельный объект можно сделать refcounted, реализуя тем самым его копируемость.


                      1. 0xd34df00d
                        05.11.2021 06:07
                        +3

                        GC не является "методом управления ресурсами", а только лишь памятью.

                        Да. Что не помешало вам вспомнить, что RAII позволяет не только это, и вы же написали:


                        RAII дает куда больше чем простое освобождение памяти.

                        На что, собственно, я и ответил, что плохо оно это даёт, типы лучше.


                        Вы пытаетесь продать исключение при выходе за границу так, словно оно предотвратило ошибку.

                        Именно так, см. ниже.


                        Однако надежность системы не выросла ни на йоту.

                        Нет, выросла.


                        Гарантии можно в первом приближении разложить в три уровня, в порядке ослабления:


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

                        Где-то на вершине условные агды с идрисами, посередине — условный хаскель (и на самом деле он очень далеко от вершины, к слову), чуть ниже — условный питон, ещё ниже — C++.


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


                        "Прервалось экзепшном" != "не работает". Максимально вероятно код будет продолжать работать каким-то не запланированным способом.

                        Объясните для тупых, как код будет продолжать работать после того, как гипотетический operator[] кинет здесь ошибку?


                        int ints[42] = {};
                        float floats[10] = {};
                        
                        ints[42] = 0;

                        А если экзепшона нет, то вы просто попортите один из флоатов, если не повезёт с их раскладкой в памяти, и всё.


                        Так же как и в плюсах.

                        В плюсах оно не прерывается экзепшоном. Как это «так же»?


                        Где в вашем сознании пролегает та самая эфемерная граница, их разделяющая? Возможно это банальная предвзятость?

                        Это не граница, это спектр.


                        Вроде логично, что любой иммутабельный объект можно сделать refcounted, реализуя тем самым его копируемость.

                        Что вообще такое рефкаунт в иммутабельном случае? Как там счётчик выглядит, что вы модифицировать собрались?


                      1. eao197
                        05.11.2021 07:04

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

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


                      1. 0xd34df00d
                        05.11.2021 08:37
                        +2

                        К счастью, кроме динамических проверок есть возможность что-то про код доказывать статически (про что я тоже писал).


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


                      1. eao197
                        05.11.2021 08:49

                        К счастью, кроме динамических проверок есть возможность что-то про код доказывать статически (про что я тоже писал).

                        И тогда рассматривать пример с динамической проверкой границ и выбросом исключения становится бессмысленным.

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

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

                        По поводу динамических проверок можно еще катастрофу Ариан-5 вспомнить, когда был переиспользован код из Ариан-4, не рассчитанный на условия взлета другой ракеты. Динамические проверки сработали, но надежности это не добавило. Потому что ошибка была на другом уровне.

                        И да, софт для Ариан-5 был не на C++, а на более безопасной Ада.


                      1. 0xd34df00d
                        05.11.2021 09:13
                        +1

                        И тогда рассматривать пример с динамической проверкой границ и выбросом исключения становится бессмысленным.

                        К сожалению, эта возможность есть не во всех языках, поэтому приходится идти на компромиссы.


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

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


                      1. Antervis
                        05.11.2021 18:55
                        -1

                        На что, собственно, я и ответил, что плохо оно это даёт, типы лучше.

                        т.е. по свойству транзитивности типы лучше GC?

                        Если у меня есть условный торговый бот

                        а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена. АЭС конечно же редкий случай, но если проект может позволить себе failsafe, то опять же варианты 2 и 3 практически иррелевантны.

                        Объясните для тупых, как код будет продолжать работать после того, как гипотетический operator[] кинет здесь ошибку?

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

                        Тем не менее я не хочу меряться чей вырожденный негативный сценарий хуже. Я лишь пытаюсь объяснить что возможность обработки ошибки сама по себе не помогает её предотвратить.

                        В плюсах оно не прерывается экзепшоном. Как это «так же»?

                        пишите at, будет прерываться.

                        Это не граница, это спектр.

                        где-то я это уже слышал. Правда, тот раздел психологии перестали преподавать в вузах.

                        Что вообще такое рефкаунт в иммутабельном случае? Как там счётчик выглядит, что вы модифицировать собрались?

                        рефкаунтинг всегда делают "под капотом", так, что снаружи выглядит словно инстанс не меняется. И я всё еще не понимаю чего вы от меня добиваетесь. Хотите чтобы я спроектировал функциональный язык без GC?


                      1. ldss
                        05.11.2021 20:29
                        +1

                        а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны - после катастрофы задача "не допустить сбой" провалена.

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

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


                      1. Antervis
                        05.11.2021 21:12

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

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


                      1. ldss
                        05.11.2021 21:29

                        вопрос-то в том, что во втором случае пофигу, есть она у меня или нет


                      1. Antervis
                        05.11.2021 21:32

                        вопрос-то в том, что во втором случае пофигу, есть она у меня или нет

                        я уже дважды привел кейс где объяснял насколько это совершенно не "пофигу"


                      1. ldss
                        05.11.2021 21:37
                        +1

                        Ну давайте сначала

                        Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок

                        И возникает ситуация, описанная выше по треду

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

                        Т.е., есть во втором случае есть у вас обработка ошибок, нет ее - совершенно никак вам не поможет


                      1. Antervis
                        05.11.2021 21:48

                        Ну давайте сначала

                        хорошо, в четвертый раз...

                        Допустим, в обоих случаях у нас есть внятная стратегия обработки ошибок

                        так я и утверждаю что её нет! Вы либо предусмотрели возможность ошибки типа X (в нашем случае выхода за границы массива), добавили доп. условие и предотвратили возникновение этой ошибки. Либо вы эту ошибку не предусмотрели, и соответственно забыли добавить для неё корректную обработку. И тогда лучшее, на что вы можете надеяться - что ошибка типа X улетит в обработчик ошибки типа Y, который волшебным образом сделает что-то отдаленно логичное для конкретной ситуации. В худшем случае последствия будут печальнее проезда по памяти.


                      1. 0xd34df00d
                        06.11.2021 00:20
                        +2

                        т.е. по свойству транзитивности типы лучше GC?

                        Не понял, причём тут транзитивность?


                        а если не торговый бот, а условная АЭС, то варианты 2 и 3 абсолютно иррелевантны — после катастрофы задача "не допустить сбой" провалена. АЭС конечно же редкий случай, но если проект может позволить себе failsafe, то опять же варианты 2 и 3 практически иррелевантны.

                        На софтовом уровне в АЭС failsafe вполне себе есть: можно просто взять и заглушить реактор (например, просто подняв или опустив стержни, в зависимости от конструкции). Да, неприятно, да, йодная яма, но не взрыв.


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


                        исключение долетит до ближайшего хендлера и продолжит выполнение оттуда. Например, сервер вернет 500-ку.

                        500-ка — это не то же самое, что «мы тут рассчитали какую-то ерунду и вернули её», не так ли?


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

                        А в случае недетектируемого выхода за границы это не так? Это тем более так — вы просто будете выдавать аналогичную ерунду на последующие запросы, так как условия совершенно аналогично не поменялись.


                        Что хуже — один битый запрос, или миллионы?

                        1. С чего он будет один?
                        2. Вопрос некорректен: что лучше, M битых ответов с данными, взятыми из пятой точки, или N ответов «честно признаюсь, что не шмогла»?

                        Я лишь пытаюсь объяснить что возможность обработки ошибки сама по себе не помогает её предотвратить.

                        Она помогает узнать, что ошибка вообще была.


                        пишите at, будет прерываться.

                        …и все свои зависимости надо проверить, что там at.


                        рефкаунтинг всегда делают "под капотом", так, что снаружи выглядит словно инстанс не меняется. И я всё еще не понимаю чего вы от меня добиваетесь. Хотите чтобы я спроектировал функциональный язык без GC?

                        Нет, я хочу, чтобы вы определились, понимаете ли вы, как работает чистое ФП (и не предлагали рефкаунты под капотом), или же не понимаете (и не пытались сказать, что ФП — это семантически C++, где всё pass-by-value).


                      1. Antervis
                        06.11.2021 08:18

                        500-ка — это не то же самое, что «мы тут рассчитали какую-то ерунду и вернули её», не так ли?

                        я пытаюсь доказать что ошибки обоих видов могут вести к довольно-таки неприятным последствиям. Вы же пытаетесь доказать что гипотетическая программа A на языке X, содержащая ошибку W, обязательно обработает эту ошибку хуже гипотетической программы B на языке Y. Совершенно игнорируя тот факт что это зависит не от языков X vs Y, а от программ A vs B. И я уже объяснил почему язык B не поможет предотвратить ошибку W сколь угодно лучше языка A.

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

                        Нет, я хочу, чтобы вы определились, понимаете ли вы, как работает чистое ФП (и не предлагали рефкаунты под капотом), или же не понимаете (и не пытались сказать, что ФП — это семантически C++, где всё pass-by-value).

                        во-первых, GC в функциональных языках по-вашему на пыльце фей работает? Там такие же счетчики ссылок под капотом. Во-вторых, вы придираетесь к той части, которая мне безынтересна. Мой аргумент состоял в том, что за счет более жестких контрактов в ФП можно обеспечить некоторые дополнительные гарантии. Ваши контраргументы это не опровергают... и вообще непонятно зачем они.


                      1. 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++ нельзя написать нетривиальную программу в иммутабельном стиле с передачей объектов по значению).


                      1. Antervis
                        08.11.2021 01:55

                        Вы спорите с соломенным чучелом.

                        А "условного торгового бота" тоже я выдумал?

                        А то, что я пытаюсь сказать — это что диагностируемые ошибки лучше недиагностируемых и скрытых.

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

                        Это деталь реализации, при обсуждении операционной (или денотационной, неважно) семантики про подобное вспоминать не имеет смысла.

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

                        Вы изначально начали говорить, что в ФП за счёт иммутабельности что-то там происходит

                        не происходит, а гарантируется

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

                        почему нельзя написать то?


                      1. 0xd34df00d
                        09.11.2021 00:40
                        +1

                        А "условного торгового бота" тоже я выдумал?

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


                        Про обязательность обработки «хуже» я ничего не говорил и даже не имел в виду.


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

                        В таком случае нам что плюсы, что всякие джавы-дотнеты вообще обсуждать не стоит изначально. Но так как мы таки их обсуждаем, то этот частный случай как-то тут не к месту рассматривать, ИМХО.


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

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


                        почему нельзя написать то?

                        Потому что рефкаунт не будет иммутабельным.


                      1. Antervis
                        09.11.2021 07:57

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

                        Но так как мы таки их обсуждаем, то этот частный случай как-то тут не к месту рассматривать, ИМХО.

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

                        Вот вы описали все ужасы порчи памяти, а я продемонстрировал, как некорректная обработка ошибки может усугубить её последствия. Это гипотетические worst case scenario, основанные на предположениях о конкретном поведении конкретного кода. В целом можно игнорировать, верно?

                        Далее, вы утверждали диагностируемые ошибки строго лучше UB. С этим я не спорю. Но вы так же утверждали что это улучшает надежность системы, с чем я не согласен по тому определению надежности, которое я привел выше. Почему это частный случай и его не имеет смысл рассматривать?

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

                        Я ж говорю не про язык, а про компилятор/рантайм. Рефкаунт может служить лишь как оптимизация копирования, применимая для всех иммутабельных объектов. Но я всё еще не понимаю к чему это всё. Изначально я утверждал, что в функциональных языках более строгие контракты и можно добиться более строгих гарантий. Если вы пытаетесь спорить с этим утверждением, то я не могу понять как ваши аргументы его опровергают.


                      1. 0xd34df00d
                        11.11.2021 09:31
                        +1

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

                        Я так и не понял, что именно усугубляется, когда вместо некорректных расчётов вы получаете ошибку 500.


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

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


                        Рефкаунт может служить лишь как оптимизация копирования, применимая для всех иммутабельных объектов.

                        Не «лишь как». Некоторые объекты принципиально некопируемы, вроде тех же несчастных соединений с БД.


                      1. eao197
                        05.11.2021 07:01
                        +2

                        Однако надежность системы не выросла ни на йоту.

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

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

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


                      1. Antervis
                        05.11.2021 19:03

                        Надежность определяется как тем, делает ли программа какую-то незапланированную фигню (выходит за пределы массива), так и тем, диагностируется ли эта незапланированная фигня или же нет (отсутствие диагностики как раз снижает надежность), так и тем, есть возможность среагировать на диагностику.

                        ну давайте рассмотрим такой сценарий. Мы хотим предотвратить ошибки с помощью их диагностики в предварительных прогонах. Например, в тестах. Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?

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

                        в тех языках страдает еще и применимость. Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?


                      1. eao197
                        05.11.2021 20:06
                        +1

                        Что нам мешает в плюсах запускать тесты с санитайзерами, которые распечатают нам все ошибки памяти?

                        Тут важнее другое: а какие гарантии нам это дает?

                        Момент первый. Вроде как еще никому не удавалось что-то более менее серьезное протестировать на всех возможных входных данных во всех возможных ситуациях.

                        Момент второй. В некоторых областях накладные расходы, которые мы вынуждены нести на санитайзеры или даже на отсутствие должных оптимизаций в debug-режимах, могут вести к существенному изменению т.н. "времянки". Из-за чего под санитайзерами просто не будут возникать те приключения, которые у нас возникнут при полной скорости работы.

                        Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?

                        У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу. Ведь, как оказалось, где-то есть инструменты поудобнее. И решать на них какие-то задачи проще, чем на C++. Боль от такого открытия и заставляет 0xd34df00d снова и снова сравнивать C++ с другими языками.


                      1. Antervis
                        05.11.2021 20:29

                        У меня сложилось впечатление, что 0xd34df00d тупо убил часть своего детства и всю свою молодость на задрачивание C++ и долгое время был вынужден использовать только C++. Даже там, где у этого могло и не быть смысла. А потом, в какой-то момент, он открыл для себя дивный чудный мир за пределами его старого узкого мирка и у него слегка снесло крышу.

                        и в итоге зачастую сравнивает с++98/03 с языками, появившимися после с++11/14. Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного. То есть аргументы вроде как правильные, но они рисуют картину, которая от реальной разработки на современных плюсах отличается как небо и земля.


                      1. ldss
                        05.11.2021 21:16

                        Я почти уверен что никто из здесь присутствующих не хотел бы разрабатывать на тех же джаве/шарпе версий 2000-ного

                        я после 8 лет на плюсах начал писать еще на c# 1.1
                        Радовался как дитя

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

                        А когда вышел 3й, стало ну совершено понятно, что с++ это забытое прошлое:)


                      1. Antervis
                        05.11.2021 21:42
                        +1

                        я после 8 лет на плюсах начал писать еще на c# 1.1
                        Радовался как дитя

                        то есть вы сейчас расписались в той же самой ошибке, за которую я критикую 0xd34df00d, и которая явно является базой вашей предвзятости.


                      1. ldss
                        08.11.2021 18:48

                        так как вы можете судить об ошибках, если ни на чем, кроме плюсов не пишете?


                      1. Antervis
                        08.11.2021 22:16
                        +3

                        так как вы можете судить об ошибках, если ни на чем, кроме плюсов не пишете?

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


                      1. ldss
                        09.11.2021 22:27
                        -1

                        так я последний раз пару лет назад писал, как раз с использованием разного - unique_ptr и прочего

                        Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.

                        А у вас, судя по всему, подобного опыта нет. Как там было, "узкий специалист подобен флюсу - полнота его одностороння"(ц)

                        По крайней мере я понимаю "надежность ПО" именно как отсутствие непредвиденных отказов.

                        ыы

                        вы, наверное, не очень давно работаете, извините

                        непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей


                      1. Antervis
                        10.11.2021 00:15

                        Мне не понравилось, слишком много надо держать в голове в сравнении с другими языками, типа с#, жава, котлин, питон.

                        на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов. Да, вроде как не беспокоишься о лайфтаймах, но почти любая опечатка ловится только в рантайме. Спасибо, я лучше на плюсах буду писать, нервы дороже.

                        вы, наверное, не очень давно работаете, извините

                        что-то около 6-7 лет, наверно не очень давно, да. За выслугу лет еще не награждали.

                        непредвиденные отказы есть всегда, как только программа попадает в б-м массовые руки пользователей

                        а если я бекенд разрабатываю, то кто есть пользователь? А если ошибки бывают, но в большинстве чисто логические, которые были бы допущены и в других языках?


                      1. ldss
                        15.11.2021 18:12

                        на жаве/котлине не писал, а вот на питоне довелось, и там надо держать в голове больше, чем в плюсах. Как минимум - типы переменных/аргументов.

                        Зачем?

                        а если я бекенд разрабатываю, то кто есть пользователь?

                        как только у вас появляется хотя бы пару тысяч пользователей, все становится интересно. Потому что то, что они могут учудить, ни одному QA в голову не придет
                        Так что ошибки/баги есть всегда; рассуждения о том, что надо писать без них - идеалистические


                      1. F0iL Автор
                        10.11.2021 00:40
                        +4

                        Прошу прощения, что встреваю в ваш спор, но вот он я. Не люблю меняться пиписьками, но раз уж зашёл разговор про опыт, скажу что за 15 лет профессиональной деятельности в IT кроме плюсов и сей я писал ещё в разное время на Delphi (упокой господь его душу), PHP (но старом, до 5ой версии), Python (в основном всякую CI/CD автоматизацию), JS/TS (и фронтенд и бэкенд), C# (не то чтобы много, но и немало, под десктоп и под ASP.net), на Go, чуть-чуть на Java, и ещё на паре языков, названия которых вы наверное даже не слышали. Поэтому мне есть с чем сравнивать, но я читаю ваши комментарии и искренне не могу понять, что и кому вы тут пытаетесь доказать.

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

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

                        То что вам не нравятся плюсы, вы на них не пишете и не хотите писать? Да вас никто не заставляет так-то, не хотите - не пишите.

                        То что все остальные должны срочно перестать писать на плюсах? Извините, нет. На плюсах есть много офигенных и интересных проектов. Прошивки IoT- и телеком-железок пишутся на плюсах. Движки веб-браузеров и виртуальные машины пишутся на плюсах. Графические движки пишутся на плюсах. И очень много чего ещё пишется на плюсах. Поэтому я тоже на них пишу и мне нравится :)


                      1. ldss
                        15.11.2021 18:17
                        +1

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

                        Меньше напрягать мозг на особенности языка. И эти высвободившиеся мощности можно вполне пустить на бизнес-логику. Это, на мой взгляд, эффективнее - все таки программы на работе мы пишем не для себя, a для пользователей, а им пофиг на язык

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

                        из того, что я вижу в обсуждении - они становятся все сложнее и с б0льшим UB

                        То что все остальные должны срочно перестать писать на плюсах?

                        Так оно само так случится, очень уж высокий порог входа и дорогая поддержка

                        Графические движки пишутся на плюсах

                        Например, Unity:))


                      1. Aldrog
                        15.11.2021 19:05
                        +1

                        Например, Unity:))

                        А на чём он по-вашему написан?


                      1. ldss
                        17.11.2021 17:52

                        и на плюсах, и на с#

                        но вся разработка с ним - на c#

                        Если с++ так чудесен, почему бы не сделать его с++ only, как тот же unreal?


                      1. Aldrog
                        17.11.2021 19:05

                        Вам говорят


                        Графические движки пишутся на плюсах

                        А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!


                        То, что скрипты игровой логики к нему пишутся на C# совершенно неважно, так как это уже другая область, в которой вообще вроде бы Lua доминирует.


                      1. ldss
                        17.11.2021 19:38
                        +1

                        А вы в ответ приводите контрпример в виде… движка, написанного на плюсах!

                        Ок, ок, ошибся

                        Core у него на плюсах, все остальное - на сишарпе

                        То, что скрипты игровой логики к нему пишутся на C# совершенно неважно

                        скрипты пишутся на lua:)
                        А игра, т.е. взаимодействие логики отображения и взаимодействия - на с#; движок просто рисует меши, грубо говоря


                      1. F0iL Автор
                        15.11.2021 22:03
                        +1

                        Так оно само так случится, очень уж высокий порог входа и дорогая поддержка

                        Вы почему-то упустили слово "срочно" :) На самом деле нытьё про то что все массово откажутся от плюсов я слышу уже лет так 15, но не смотря на активное развитие другие языков за эти годы, не смотря на рост вычислительных мощностей, не смотря на приток огромного количества новых людей в индустрию, чьи мозги не были травмированы олдскульным кодингом, в большинстве упомянутых мною сфер плюсы по-прежнему лидируют и даже понемногу проникают в смежные.
                        Так что оно случится рано или поздно, но явно не в ближайший десяток лет. Скорее всего в большинстве упомянутых применений их вытеснит Rust, что в общем-то неплохо, но идти этот процесс будет очень медленно.

                        из того, что я вижу в обсуждении - они становятся все сложнее и с б0льшим UB

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

                        Например, Unity:))

                        Вы, должно быть, хотели блеснуть остроумием, но я вас расстрою - движок Unity написан на C++, шарп там используется только для вытаскивания апишек движка наружу.


                      1. ldss
                        17.11.2021 17:57

                        в большинстве упомянутых мною сфер плюсы по-прежнему лидируют и даже понемногу проникают в смежные.

                        Ну вы посмотрите на тренд за последние лет 10-15

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

                        Вы теоретизируете о чем-то что где-то услышали

                        Я всего лишь читаю данную дискуссию

                        Вы, должно быть, хотели блеснуть остроумием, но я вас расстрою - движок Unity написан на C++, шарп там используется только для вытаскивания апишек движка наружу.

                        только?:) Т.е., то, что основной интерфейс для работы с юнити это с#, это только?


                      1. Antervis
                        17.11.2021 20:14

                        Ну вы посмотрите на тренд за последние лет 10-15

                        Использование плюсов неуклонно снижается.

                        так уж прям неуклонно снижается? По данным stackoverflow доля с++ опять же заметно выросла за последние лет пять.


                      1. ldss
                        17.11.2021 21:30

                        я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко

                        ну и в вашей ссылке речь о С :)

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


                      1. Antervis
                        17.11.2021 22:36
                        +2

                        я потому и предлагаю смотреть на 10-15 летние тренды. То, что сейчас поднялся - ну ок, но до уровня его массовости в начале 2000х, например, куда как далеко

                        а потом мощности компьютеров стали позволять писать даже на языках с GC

                        ну и в вашей ссылке речь о С :)

                        вчитайтесь

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

                        Во-первых, с++ сильно производительнее большинства из этой массы современных языков. Экономить ресурсы на высокоуровневом ЯП куда сложнее, например, память в джаве или cpu в питоне. Во-вторых, я же никогда не пытаюсь доказать что с++ - самый безопасный и самый выразительный язык. Мой посыл как раз в том, что с годами он стал сильно удобнее и дал больше средств для обеспечения надежности. Настолько, что размен производительности на выразительность/безопасность становится куда менее очевидным. Большая часть ошибок, с которыми лично мне доводится иметь дело, чисто логические, которые я или мои коллеги допустили бы в любых ЯП. Что до меньшей части - ну буду я не дебажить краши, а оптимизировать боттлнеки, в общем случае это не быстрее.


                      1. ldss
                        18.11.2021 22:12

                        а потом мощности компьютеров стали позволять писать даже на языках с GC

                        Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа

                        Во-первых, с++ сильно производительнее большинства из этой массы современных языков.

                        по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее; да и скорость выделения в managed/unmanaged heap мы уже обсуждали

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

                        другие-то тоже на месте не стояли:)

                        вопщем, с моего имха я б не замыкался в рамках одного языка


                      1. Antervis
                        18.11.2021 23:06

                        Да, как вариант; я сразу сказал, что труд погромиста сильно дороже железа

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

                        по размеру памяти - возможно. Чисто как числодробилка - где как, и совсем не "сильно" производительнее;

                        класс приложений-"числодробилок" куда меньше класса приложений, работающих с текстом.

                        да и скорость выделения в managed/unmanaged heap мы уже обсуждали

                        и кажется вы из этого обсуждения ничего не вынесли

                        другие-то тоже на месте не стояли:)

                        ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...

                        вопщем, с моего имха я б не замыкался в рамках одного языка

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


                      1. ldss
                        19.11.2021 01:30

                        в частном случае да, в общем - нет

                        наоборот

                        В общем случае железо дешевле

                        По факту совсем не париться о производительности обычно могут позволить себе только фронтендеры (потому что за железо платит пользователь) и ML специалисты

                        ну-ну
                        наша контора продает, например, десктопный софт, который стоит (в полном сборе) 40000 баксов за 1 (одно) рабочее место. Даже супермощный десктопный комп будет стоить сильно меньше

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

                        и кажется вы из этого обсуждения ничего не вынесли

                        мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете

                        ну в качестве примера аналоги auto и лямбд появились в java позже с++, хотя казалось бы...

                        а в дотнете они появились 14 лет назад

                        да, я всё не могу выделить время поучить раст

                        смотрите, потом поздно будет


                      1. Antervis
                        19.11.2021 11:24

                        наоборот В общем случае железо дешевле

                        как у вас частное перестало быть подмножеством общего?

                        наша контора продает, например, десктопный софт, ...

                        и этот частный пример по-вашему задает общие тенденции?

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

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

                        мне кажется, это вы должны были что-то вынести, т.к. совсем себе не представляли, как работает выделение/освобождение памяти в хотя бы дотнете

                        вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением


                      1. ldss
                        19.11.2021 21:37

                        как у вас частное перестало быть подмножеством общего?

                        потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов

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

                        Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку

                        вы просто не поняли обоснование того, как GC даже теоретически не способен экономить ресурсы по сравнению с автоматическим освобождением

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


                      1. Antervis
                        19.11.2021 22:09

                        потому что я не согласен с вашим утверждением. Да, собственно, это уже давно общее место, что железо дешевле софта, и соответственно, труда программистов

                        "в среднем" - может быть, "в общем" - нет.

                        Может, и что? Дешевле и быстрее заплатить за железо, чем за дополнительную разработку

                        опять же, это "иногда" а не "всегда".

                        Если комплексно о разработке, то сильно экономит ресурсы; человеческие, в первую очередь

                        ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах. Чтобы достигнуть такого уровня производительности на с++ даже задумываться не приходится. Как думаете, сколько человеческих ресурсов мне и моим коллегам бы сэкономило если бы всё просто работало?


                      1. ldss
                        20.11.2021 01:20

                        "в среднем" - может быть, "в общем" - нет.

                        в среднем и чаще всего
                        Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"

                        опять же, это "иногда" а не "всегда".

                        да почти всегда

                        доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле

                        ну вот буквально сегодня я словил факап на проде потому что, как оказалось, кеширующая прокся на go не способна держать жалкие 2k rps на 4-х ядрах.

                        так тут либо а) прокся просто криво написана б) либо да, затык по производительности. Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра

                        При чем тут язык?


                      1. Antervis
                        20.11.2021 03:31

                        Речь не идет о высокопроизводительном софте, которого проценты от общего. 90% софта вполне попадают под "общее"

                        откуда вы взяли цифру в 90%?

                        доп дев обойдется конторе в 70-100 тыс в год (это в нашем небогатом квебеке, например), нарастить облако будет сильно дешевле

                        а с чего вы взяли что доп разраб обязательно потребуется? И с чего вы взяли что он не окупится? И с чего вы взяли что дешевле? Из вашего конкретного случая или из головы?

                        так тут либо а) прокся просто криво написана б) либо да, затык по производительности.

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

                        Я б поставил на первое, особенно если выяснится, что данные хреново параллелятся на эти самые 4 ядра

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


                      1. ldss
                        21.11.2021 23:47

                        откуда вы взяли цифру в 90%?

                        да вакансии посмотрите на работных сайтах

                        а с чего вы взяли что доп разраб обязательно потребуется?

                        а они всегда требуются, если мы хотим чего-то большего

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

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

                        если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст

                        Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин


                      1. Antervis
                        22.11.2021 01:09

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

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

                        если программа хреново написана, то да. Просто потому, что увеличение числа ядер ничего не даст

                        вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно

                        Но это от языка не зависит вообще; я-то говорил о ситуациях, когда ваше аккуратное использование памяти легко бьется покупкой доп планок (если совсем примитивно), или доп. виртуальных машин

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


                      1. ldss
                        23.11.2021 06:11

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

                        увы, это реалии отрасли

                        вы забываете что с++ куда лучше масштабируется по числу ядер чем языки с GC. Из-за самого GC, собственно

                        эээ.. это, мягко говоря, слабо обоснованное умозаключение

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

                        эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске


                      1. Antervis
                        23.11.2021 10:14

                        увы, это реалии отрасли

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

                        эээ.. это, мягко говоря, слабо обоснованное умозаключение

                        Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.

                        А конкурентные GC вводят дополнительные синхронизации потоков, стоимость которых снова квадратно пропорциональна числу активных потоков.

                        И в обоих случаях стоимость GC не является пренебрежимо малой даже на малом числе потоков. Выглядит как формальное доказательство.

                        эээ.. gc чаще всего и распределяет ее б-м идеально - в непрерывном куске

                        я имел в виду распределение нагрузки между виртуальными машинами.


                      1. ldss
                        24.11.2021 18:02

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

                        кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать

                        Если у нас stop the world GC: чем больше потоков плодят мертвые объекты, тем дольше цикл сборки мусора, который останавливает все потоки, т.е. суммарное время простоя ядер квадратно пропорционально числу активных потоков.

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


                      1. Antervis
                        24.11.2021 18:10

                        кто ж ее через два года переписывать будет-то:)) Бабло надо рубить, а не переписывать

                        так у вас софтина не работает как надо, клиенты перетекают к конкурентам, поток бабла постепенно снижается, если вовремя не переписать бабло закончится. Это с точки зрения бизнеса.

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

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

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


                      1. ldss
                        25.11.2021 22:54

                        так у вас софтина не работает как надо, клиенты перетекают к конкурентам

                        это если они есть, и у них не хуже

                        Обычно, у них такие же проблемы. Большинство софта, который на рынке давно и даже стандарт - редкостное г в плане кода, там просто чюдовищный легаси

                        С точки зрения отдельного разработчика конечно же интереснее из раза в раз писать поделия-однодневки

                        поделия очень разные бывают. Можно пилить сайты, когда вся работа сведется к перекладыванию json-ов из таблички в табличку, а может и нет. Я вот, помню, работал на одну офшорку - ни одной одинаковой задачи не было за 2 года. Может, повезло, конечно

                        может быть и много, но никогда не бесконечно

                        ну так и нормально написанное приложение не увеличивает память постоянно


                      1. Antervis
                        26.11.2021 05:14

                        Обычно, у них такие же проблемы

                        а если у кого-то из конкурентов это не так, можно сразу сворачиваться?

                        ну так и нормально написанное приложение не увеличивает память постоянно

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

                        Можно пытаться выделять все объекты/буферы заранее, а потом работать уже с ними. Но, опять же, не в языках с GC


                      1. 0xd34df00d
                        06.11.2021 00:41
                        +1

                        и в итоге зачастую сравнивает с++98/03 с языками, появившимися после с++11/14

                        Я не сравниваю C++98/03. Я сравниваю C++17/20.


                        Или в вашей версии C++20 что-то принципиально поменялось по части гарантий со стороны языка?


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

                        Неа, не отличается. На C++20 писать в прод мне не приходилось, но на C++17 — вполне.


                      1. 0xd34df00d
                        06.11.2021 00:39
                        +1

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

                        То, что нет никаких гарантий распечатывания всех ошибок памяти.


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


                        Смысл спорить какой язык, A или B, подойдет для задачи X лучше, если B для этой задачи в принципе не применим?

                        Почему неприменим-то?


                      1. Antervis
                        06.11.2021 08:34
                        +1

                        Неа, не отличается. На C++20 писать в прод мне не приходилось, но на C++17 — вполне.

                        расскажите подробнее. Сколько, объем проекта, какого размера команда, насколько там современный код?

                        Или в вашей версии C++20 что-то принципиально поменялось по части гарантий со стороны языка?

                        начиная с с++11 появляется всё больше инструментов, позволяющих решать задачи быстрее и надежнее. Кодобаза на новых плюсах будет в среднем в разы лаконичнее и надежнее её старого аналога.

                        То, что нет никаких гарантий распечатывания всех ошибок памяти.

                        скорее гарантируют чем нет. По крайней мере я не знаю сценарий, способный обмануть санитайзер.

                        Я уж не говорю о логистических проблемах сборки всех зависимостей с санитайзерами, о повышенном потреблении ресурсов

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

                        ... о том, что некоторые ошибки проявляются только сильно спустя время, и так далее.

                        и в условном шарпе таких ошибок, не сразу проявляющихся, конечно же не бывает?

                        Почему неприменим-то?

                        Если хотите поспорить на тему "почему языки с GC не применимы в latency-critical системах" можете перейти в эту статью


                      1. 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-представления).


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


                      1. 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)

                        Я согласен, что чем короче/проще код, тем сложнее спрятать в нём ошибку. Собственно, на этом моя аргументация и сторится - код на с++ действительно стал короче и проще. А ваша позиция - "по сути ничего не поменялось". Так тогда возникает закономерный вопрос - ваше мнение изменится если скажем в с++ появится рефлексия?


                      1. 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++ этим всем не пользуется, и вполне оправданно.


                        Так тогда возникает закономерный вопрос — ваше мнение изменится если скажем в с++ появится рефлексия?

                        Я боюсь, что рефлексии будет недостаточно по куче разных причин, но это совсем другая история.


                      1. 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+. Что кстати тоже сокращает код. Могли бы мб сделать явный квалификатор для этого, но их уже и так много...

                        Я боюсь, что рефлексии будет недостаточно по куче разных причин

                        ну для задач типа "сериализуй эту структуру с политикой по умолчанию" более чем хватит.


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

                        Комментарии:


                        1. Ой, экзепшоны для управления control flow. Щито поделать десу.
                        2. Обратите внимание, что замена at на operator[] не сломает компилябельность этого кода, но поменяет семантику. Если завтра ваш код придёт мейнтейнить мид, который где-то прочитал, что operator[] быстрее/лучше/етц, и заменит его здесь, код просто тихо перестанет кидать экзепшон. Потому что список экзепшонов не видно в типах, а Maybe — видно.
                        3. Обратите внимание, что проверка на длину массива и доступ к элементу разнесены синтаксически, и проверку можно случайно забыть, от чего компилябельность программы не пострадает. Можно было бы совместить через vector::at вместо operator[], завернув заодно в hoistNothing, но довольно забавно, что это будет медленнее паттерн-матчинга в хачкеле.
                        4. Ой, сырые указатели. So much for modern C++.
                        5. Ой, сырые указатели. So much for modern C++.
                        6. Снова обратите внимание, что проверка и доступ синтаксически разнесены. Снова проверку можно забыть. Этого вообще, принципиально не может произойти с паттерн-матчингом.
                        7. Ура, тут мы стали C++20-only, потому что до C++20 нельзя капчурить structured bindings в лямбды.

                        Кроме того, код на хаскеле я писал, не особо думая, как только понял структуру ответов от neovim'а. Код на плюсах мне пришлось писать минут 40 и подключая мозг и cppreference.


                        В качестве задачи со звёздочкой предлагаю прикинуть, что нужно будет сделать, чтобы вместо std::optional<T> возвращать аналог Either String T с ошибкой, которая плюс-минус поможет вам как автору потом понять, что именно сломалось. Мне в хаскель-версии потребуется по большому счёту только добавить вызов maybeToRight (который, кстати, можно найти по типам) в двух местах (заменив, например, V.find (findById markId) marks на maybeToRight "finding inmarksfailed" $ V.find (findById markId) marks) и написав руками


                        instance MonadFail (Either String) where
                          fail = Left

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


                        Статью написать, что ли.


                      1. 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 не нужен, все её использования спокойно заменяются на return std::nullopt, а реальное экономие на исключениях у вас всего несколько строк внутри лямбд, что вы в общем-то скомпенсировали hoist'ом.

                        Конечно же код с optional был бы еще короче будь в с++ монадические интерфейсы, их не хватает, и с этим я согласен.

                        Ура, тут мы стали C++20-only, потому что до C++20 нельзя капчурить structured bindings в лямбды.

                        можно, через [&foo=foo]. Согласен, хак.


                      1. 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> и проверять её. А с исключениями у вас есть три варианта:


                        1. Ловить в вызывающем коде любое исключение и интерпретировать его как Nothing. Ну так себе идея, ИМХО.
                        2. Ловить в вызывающем коде только std::out_of_range и std::bad_variant_access — которые совершенно не видно в типе функции, которые являются деталями реализации, но при этом протекают в её контракт. Это ад с точки зрения поддержки и композабельности функций.
                        3. Ловить в вызывающем коде какой-то один конкретный вид исключений (тот же 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.


                      1. 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()?

                        Да, хаскель версия всё еще короче. Но не настолько короче и не настолько читаемее (если), чтобы говорить о какой-то кардинальной разнице.


                      1. 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 кидает исключения.

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


                        1. Вы не уверены при рефакторинге, что вы ничего не сломали.
                        2. Вы не уверены, что вы обрабатываете те и только те ошибки, которые хотите обработать.
                        3. Компилятор вам больше не друг и не помощник в этом всём.

                        Поэтому их в любом языке лучше избегать.


                      1. Antervis
                        18.11.2021 03:40

                        Как это не потерял? Ваш вариант применяет функцию к аргументам в прямом порядке, мой — в обратном

                        я не то имел в виду

                        Зависит от вашего опыта с шаблонным метапрограммированием, тащем-та. Для значимой части людей всё это — нечитаемые трюки.

                        для людей, которые не освоились с с++11 то? Ну хз, хз. Да, я знаю что fold ввели в с++17, но его можно эмулировать в с++11

                        Помогают не думать о том, есть ли у какого-то другого поля в variant'е неявное преобразование из инта, и как variant с этим будет работать, особенно если я завтра поменяю int на uint64_t — плюсы не настолько дружественны к механическому рефакторингу, чтобы не думать об этом наперёд

                        это можно делать либо type alias'ом, либо, на совсем худой конец, через enum class foo : int; - создает distinct type. Точно так же как class ObjectString : 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?

                        Поэтому их в любом языке лучше избегать.

                        удачи избегать их в доброй половине ЯП, ага


                      1. csl
                        15.11.2021 07:49
                        +1

                        "Статью написать, что ли."

                        Я бы почитал пост.


                      1. eao197
                        12.11.2021 11:10

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

                        А попробуйте спроецировать эту задачу на другие языки программирования. Ну там C#/Java, Haskell, Rust. За счет чего бы вы получили упрощение своей работы и какую бы производительность/предсказуемость получили бы в итоге (равно как и функциональность)?

                        Уж не за счет ли наличия GC в C#/Java/Haskell (с boxing-ом)? И не за счет ли отсутствия стандарта как такового у Rust-а?


                      1. Antervis
                        12.11.2021 13:45

                        И не за счет ли отсутствия стандарта как такового у Rust-а?

                        ну раст не умеет ни в исключения, ни в нетривиальный мув, поэтому логика контейнера очень сильно упрощается. А еще в расте считается, что всё что safe и скомпилилось не содержит UB. Правда, для реализации полноценного буфера понадобится unsafe, а там гарантий практически нет...


                      1. warlock13
                        12.11.2021 13:58

                        раст не умеет ни в исключения, ни в нетривиальный мув, поэтому логика контейнера очень сильно упрощается

                        К сожалению или к счастью panic-safety всё же требуется от любой корректной библиотеки. А это почти то же самое, что exception-safety. (И хотя я не представляю чтобы вменяемый человек собирал программу с настройкой, отличной от `panic = abort`, у других разработчиков - разработчиков приложений - может быть иное мнение, которое мне как разработчику библиотеки приходится учитывать.)

                        А преимущество Rust в том, что для каждой unsafe функции в её документации есть раздел "Safety" и требования там простые и ясные. Есть, конечно,ещё ряд требований общего характера, среди которых самое сложное - это соблюдение "stacked borrowing", но в основном всё-таки при выяснении что в Rust UB, а что нет ты удивляешься, когда узнаёшь, что _что-то не является UB_, а в плюсах, к сожалению, наоборот.


                      1. Antervis
                        12.11.2021 15:07

                        К сожалению или к счастью panic-safety всё же требуется от любой корректной библиотеки

                        а компилятор этого требует? В смысле "скомпилирует ли код, не удовлетворяющий panic safety"? Так или иначе, тривиальность мува сильно упрощает задачу (а точнее, отсутствие необходимости поддерживать исключение/панику при муве).

                        А преимущество Rust в том, что для каждой unsafe функции в её документации есть раздел "Safety" и требования там простые и ясные. 

                        так речь же не об использовании unsafe функций, а о написании unsafe кода. Там с гарантиями всё намного хитрее, и стандарта, по которому можно было бы перепроверить код, попросту не существует.

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

                        это всё круто, учитывая что состав UB rust зависит от llvm, поведение миддленда которого по большей части определяется стандартом плюсов...


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

                        Да нет, не зависит. Но если вы можете привести конкретный пример, мне было бы интересно.


                      1. 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".


                      1. warlock13
                        15.11.2021 06:39

                        it is Undefined Behavior for U to be larger than T

                        Где это вы такое нашли? Это неправда.


                      1. Aldrog
                        15.11.2021 12:49

                        Беглый гугл подсказал, что это о копировании с помощью mem::transmute_copy<T, U>, не о касте.


                      1. Antervis
                        15.11.2021 13:34

                        Где это вы такое нашли? Это неправда.

                        Здесь: "mem::transmute_copy<T, U> somehow manages to be even more wildly unsafe than this. It copies size_of<U> bytes out of an &T and interprets them as a U. The size check that mem::transmutehas is gone (as it may be valid to copy out a prefix), though it is Undefined Behavior for U to be larger than T".

                        По умолчанию компилятор такое не пропустит, если вам от этого легче

                        Беглый гугл подсказал, что это о копировании с помощью mem::transmute_copy<T, U>, не о касте.

                        Оттуда же: "Also of course you can get all of the functionality of these functions using raw pointer casts or unions, but without any of the lints or other basic sanity checks. Raw pointer casts and unions do not magically avoid the above rules."


                      1. warlock13
                        16.11.2021 07:11

                        Ну всё-таки здесь нет однозначного запрета. И на самом деле в Rust кастить указатели можно любой к любому, а правила разименования того что получилось _менее строгие_ чем в C++.


                      1. Antervis
                        16.11.2021 10:16

                        Ну всё-таки здесь нет однозначного запрета. И на самом деле в Rust кастить указатели можно любой к любому, а правила разименования того что получилось менее строгие чем в C++.

                        в rust некорректный каст указателя уже является UB, и это куда более строгий запрет, чем в с++. А потом разыменовывай сколько влезет, ага.


                      1. Aldrog
                        16.11.2021 13:28

                        Ну камон, то что вы цитируете про размеры типов очевидно относится не к касту, а к операции «каст + копирование».


                        Raw pointer casts and unions do not magically avoid the above rules.

                        И вот эта фраза никак этого не отменяет.


                      1. 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'а.

                        Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?


                      1. Aldrog
                        16.11.2021 14:50

                        весьма очевидно что UB кроется в касте, а не в копировании

                        Не очень понимаю, почему для вас копирование sizeof<U> > sizeof<T> байт из переменной типа T очевидно не является UB, а каст является.


                        Она буквально и прямым текстом говорит что касты указателей никаким волшебным образом не обходят правила transmute/transmute_copy.

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


                        Согласен, что раздел о кастах выглядит не совсем полным, но в списке UB кастов тоже нет, только разыменования.
                        Да и в многочисленных предупреждениях о незавершённости модели памяти тоже говорится о разыменованиях, а не о самих кастах.


                        Неужели вы всерьез готовы глорить раст даже вопреки его единственной документации?

                        Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.


                      1. 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. Ну это так, к слову.

                        Я ни в коем случае не глорю раст, просто прочитав ту же самую документацию, я в ней вижу совершенно другие утверждения, чем вы.

                        значит вопрос в том, кто из нас смотрит в документацию и видит в ней то, что там написано, верно?


                      1. 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 при определённых кастах.


                      1. 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'ы.


                      1. 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'ы.

                        Всё-таки не «распространяется на», а «не обходится ими».


                      1. 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'ы.


                      1. 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 типы; с толстыми ситуация несколько иная, но так же интуитивно-ясная).


                      1. Antervis
                        17.11.2021 11:22

                        Как справедливо заметили, transmute_copy принимает параметром не указатель, а ссылку, и это всё меняет.

                        приведение указателя к ссылке считается dereference'ом?

                        Ещё в списке не упонимается pointer provenance - тоже формально не описанное (или недоописанное) UB, но хорошо известное и принимаемое во внимание теми, кто пишет unsafe код. В остальном, список полный.

                        так pointer provenance и является причиной почему такой каст может приводить к UB...


                      1. 0xd34df00d
                        15.11.2021 01:09
                        +1

                        В буфере, который был мне нужен, тоже не было ни исключений, ни нетривиальных мувов. Это был буфер, считайте, для обёрток над интами (у которых тоже бывают лайфтаймы, однако).


                      1. 0xd34df00d
                        15.11.2021 01:07
                        +2

                        Нет, разница тут в том, что C++ — единственный язык (из известных мне, по крайней мере), в котором


                        1. есть концепция лайфтаймов объектов, и
                        2. лайфтаймы объектов не привязаны к лексическому скоупу имён, и
                        3. компилятор имеет право пользоваться нарушением лайфтаймов для оптимизации, и
                        4. компилятор имеет право молчать как партизан, когда он этим таки пользуется.

                        Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.


                      1. eao197
                        15.11.2021 07:08

                        из известных мне, по крайней мере

                        Из тех, с чем дело имел я, это еще и чистый Си и Pascal. И, если мне не изменяет склероз, Modula-2 и Ada. На счет Fortran-а не в курсе, дел не имел и даже не интересовался.

                        Этого нет ни в расте, ни в хаскеле, ни, насколько я знаю, во всяких сишарпах-джавах.

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


                      1. 0xd34df00d
                        15.11.2021 21:11
                        +1

                        Из тех, с чем дело имел я, это еще и чистый Си и Pascal.

                        В C всё сильно проще (понятие лайфтайма там есть, но понятия конструктора там нет). С паскалем я дел не имел, увы.


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

                        Потому что их спеку писали не социопаты?


                        GC помогает только с одной половиной лайфтайма: с удалением объекта. С конструированием объекта GC не помогает, а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как int, до C++20 — даже если это кусок памяти от malloc(sizeof(int))).


                        И какой ценой в Rust-е попробовали достичь того же без GC (и сколько времени ушло на то, чтобы реальная альтернатива старым языкам без GC появилась в лице того же Rust-а).

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


                        Большая цена, согласен.


                      1. 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 type T" (std::ptr::read_unaligned).


                      1. 0xd34df00d
                        17.11.2021 23:23
                        +1

                        мне кажется вы просто разную планку ставите. Когда речь о с++, вы считаете что код работает только если он на 100% соответствует стандарту.

                        Хоть один компилятор имеет в своей документации фразу «даже невалидный по стандарту код, компилируемый сегодня нашим компилятором, будет компилироваться и иметь то же поведение всегда»? Покуда нет, лучший источник правды о том, как писать плюсовый код — Стандарт, и практика показывает, что компиляторы имеют свойство с новыми релизами ломать старый код и пользоваться ранее игнорировавшимися UB.


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


                        а еще в них нельзя делать то, что можно в плюсах.

                        Например?


                      1. Antervis
                        18.11.2021 03:56

                        Хоть один компилятор имеет в своей документации фразу «даже невалидный по стандарту код, компилируемый сегодня нашим компилятором, будет компилироваться и иметь то же поведение всегда»?

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

                        Я не могу вспомнить ни один такой случай...

                        "не могу вспомнить" и "не было" это разные категории.

                        ... который при этом не считался бы багом

                        "считается багом с точки зрения компилятора" и "считается багом с точки зрения стандарта" это тоже разные категории. В других языках просто нет последней инстанции в виде стандарта, и в них вас совершенно устраивают гарантии компилятора. Допустим завтра появится стандарт rust - ринетесь ли вы переписывать весь свой код чтобы он стал conformant?

                        Например?

                        ну с тем же rust простейший пример - каст & в &mut это сразу UB. А языки более высокого уровня либо в принципе не дают контроля над памятью, либо проще уж писать на плюсах, чем скажем на джаве, но не пользуясь GC.


                      1. eao197
                        16.11.2021 07:21

                         С конструированием объекта GC не помогает, а в C++ есть проблемы и с этим (вы не можете просто взять кусок памяти и проинтерпретировать его как int, до C++20 — даже если это кусок памяти от malloc(sizeof(int))).

                        И как с этим помогает GC? Я так понимаю, что в используемых вами языках с GC такая операция является штатной и делается на раз-два, не так ли?


                      1. csl
                        15.11.2021 07:41

                        Да, в Java такого нет.


  1. mrbald
    01.11.2021 11:27
    +1

    Пишу на C++ (всё, что понимают последние Clang/Gcc в RHEL). Знаю много хороших трюков и полезных библиотек. Никогда нигде не пишу и не говорю, что знаю C.

    Отучить себя от исключений, RAII, кодогенерации с шаблонами и других "удобств" и вместо одной стандартной библиотеки знать все ключевые особенности target platform programming interfaces (POSIX, BSD, etc.) совсем не просто.


    1. warlock13
      09.11.2021 03:56
      -1

      Неужели есть люди, которые пишут на C++ с включённым RTTI и исключениями? Мне трудно представить такое.


  1. anamnesis875
    03.11.2021 12:23
    +1

    Был случай...пришел на собеседование (товарищ пригласил). До этого писал на С и всякими аппаратными вещами занимался (но не разработкой с нуля). Сказал перед началом: "На С++ писал только сервисную муть для себя же, так что ничего профессионального не умею". Поспрашивали про области видимости, когда создаются static...и конечно про volatile)) ах, да и про диодный мост, RS422 (почему диф. сигнал такой хороший)...но в конце все-равно начали спрашивать про С++...я ничего не смог, но мне сказали "ничего страшного, вот есть хорошие книги". В итоге их компании прошлось отказаться от С++ в пользу С, ибо там было импортозамещение и памяти не хватало (в это можно поверить) и времени между циклами. Так что некоторое время можно еще пожить как в прошлом) Про компанию ничего конкретного не знаю, ибо остался на своем любимом заводе))


  1. Myxach
    17.11.2021 10:37

    Использует C-style массивы вместо std::array;

    Стоп, а на C++ что уже массивы с квадратными скобками устарели?

    Ну и кстати, тут многие вещи это результат не "Пришел с си", а "Программиует на C++ больше 10лет и не изучал новые стандарты."


    1. SinsI
      18.11.2021 19:26
      +1

      Нарушают кучу всего в CPP Core Guidelines - небезопасны, теряют размер при передаче как аргументов функции...

      Так что да, устарели.