В конце прошлого года 71 год исполнился Бьерну Страуструпу – создателю C++, одного из самых сложных и интересных языков программирования. Мы в ABBYY любим C++, ведь он лежит в основе и наших технологий компьютерного зрения, и используется в алгоритмах обработки естественного языка, да и опенсорсная библиотека ABBYY NeoML опирается на «плюсы».

По случаю дня рождения Бьерна Страуструпа мы поговорили с Дмитрием, руководителем группы разработчиков, которые создают в компании технологии для интеллектуального анализа бизнес-процессов. Мы уже немного рассказывали об этом решении в одном из постов. Дима работает на C++ уже 15 лет, начинал еще до того, как появился так называемый modern C++ (C++11/14 и выше). Он рассказал о том, как впервые столкнулся с C++, какие возможности есть у этого языка и что советует тем, кто только начинает погружаться в его основы или хочет прокачаться в теме.

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

Первую серьезную программу на С++ я написал 15 лет назад. Это было десктопное GUI-приложение для мониторинга радиочастотной обстановки. Оно умело управлять «железками» через физические интерфейсы передачи данных RS-232 и Ethernet, а также получало данные с оборудования. Тут понадобились еще и сокеты, самодельные протоколы общения, упаковка данных и так далее. Еще мое приложение позволяло отображать разные real-time графики, построенные на основе полученных данных. В качестве GUI-фреймворка использовался ныне уже забытый всеми VLC от Borland.

Чем тебе нравится C++?

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

Из общих фич больше всего привлекает мультипарадигменность: здорово, когда есть гибкость в выборе парадигмы написания кода. Он может быть и Object Oriented, и Generic, и Procedural, и Functional.

Вторая классная особенность «плюсов» – это кросс-платформенность. Удобно написать код единожды и при этом иметь возможность запустить его где угодно, без большого переписывания и адаптации. Впечатляет и перечень поддерживаемых целевых платформ. Можно писать и для Windows, и для Linux, и для MacOS, Android, IOS, и даже bare metal! Один из современных основополагающих принципов развития языка – это zero-overhead principle. “What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better”.

Из фич языка наиболее полезными я считаю:

  • RAII, так как используется детерминированный life-time/захват ресурсов;

  • templates, поскольку позволяет разрабатывать в Generic Programming парадигме;

  • noexcept – так как удобнее заявить, что функция не кидает исключение вовсе, нежели расписывать все возможные исключения, которые она может выкинуть (привет deprecated/removed throw specifier, а также Java :);

  • constexpr - очень здорово, что часть вычислений переносится из run-time в compile-time.

В стандартной библиотеке variant/optional - очень важные и удобные кубики! Как только без них раньше жили? :)

Отмечу также ожидаемые фичи. Они или только-только попали в новый С++20-стандарт либо же запланированы в будущих версиях. Это модули, корутины, ranges, contracts, reflection + metaclasses, as/is.

А чем не нравится C++?

С++ был моим первым и единственным языком долгое время, поэтому проблемы я заметил не сразу. Ситуация начала меняться несколько лет назад, когда я стал расширять свой основной инструментарий новыми языками: Python, C#, Dart + Flutter. Именно благодаря этому у меня открылись глаза на многие недостатки С++ и сопутствующего ему окружения. Вот некоторые из них:

  • Нет стандартного и удобного пакетного менеджера. На интеграцию той или иной third-party, SDK могут уйти часы, а иногда и дни, в то время как в других языках это делается одной командой. Кстати, возможно, именно отсутствие стандартного пакетного менеджера вынуждает многих авторов публиковать свои библиотеки как headers-only.

  • Нет по-настоящему стандартной и удобной кроссплатформенной сборочной системы. Конечно, есть СMake, но даже с ним не все так однозначно. У CMake крайне корявый и over-verbose синтаксис. Поэтому существуют многочисленные «лагеря несогласных» с «де-факто стандартным» CMake: Make/Ninja/Meson/Build2 и другие. До сих пор нет по-настоящему удобной интеграции с IDE (та же VisualStudio).

  • Нет стандартного и удобного wizard для создания (layout всех файлов и пр.) и последующего управления кроссплатформенным С++-проектом: сборки, публикации, миграции на новые версии. Достаточно один раз воспользоваться Flutter/.NET CLI, чтобы понять, что С++ есть еще куда расти и развиваться.

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

Любое предложение в язык проходит много итераций и правок, и если у автора нет времени и мотивации на подобные исправления, то его proposal будет «выпекаться» годами, а может и вовсе «протухнуть». И даже если фича все-таки попала в следующий стандарт, через три года, его еще необходимо реализовать и поддержать внутри разных компиляторов. На это может уйти еще несколько лет. Поправить найденные баги – еще год. И вот, наконец, спустя семь лет, можно попробовать ту самую фичу! Только мотивация, а возможно, и необходимость в ней, уже исчезла.

Для меня такой долго ожидаемой, хотя и не единственной, фичей стали метаклассы. Впервые о них я услышал в 2017 году, посмотрев видео с конференции cppcon (CppCon 2017: Herb Sutter “Meta: Thoughts on generative C++”). По самым оптимистичным оценкам, ожидать метаклассы можно лишь для С++ 26-го стандарта. Учитываем типичный гэп на поддержку стандарта внутри известных компиляторов, а также сделаем надбавку за консервативность индустрии – и вот мы уже приближаемся вплотную к 2030 году. А вот буду ли я продолжать заниматься разработкой на C++ через восемь лет – еще неизвестно.

Еще одна особенность C++ – это слишком большая гибкость языка. Конечно, с одной стороны, чем гибче язык, тем больше областей применения он сможет охватить и больше разработчиков привлечь. С другой стороны, из-за той же сверхгибкости страдает скорость разработки. Почти на каждом участке кода приходится применять микрорешения: оценить trade-off, pros/cons того или иного способа вместо того, чтобы полностью сконцентрироваться на разработке бизнес-логики.

Пожалуй, у меня лично самый частый вид микрорешений связан с производительностью. В этом есть определенный парадокс: мы используем C++, а не C#, например, именно потому, что хотим добиться максимальной производительности. Но есть «золотой» постулат: Premature optimization is the root of all evil. Найти прагматичный баланс между этими двумя крайностями бывает непросто, и сам факт того, что это равновесие надо постоянно искать и тратить на его поиски время разработки, удручает.

  • Обратная совместимость – один из ключевых столпов языка С++, возможно, даже главный фактор его долголетия. Однако из-за того, что необходимо поддерживать решения из прошлого века, сложность языка многократно возрастает и появляется 100+ способов «выстрелить себе в ногу». Было здорово найти безболезненный для индустрии способ, чтобы отсечь от языка все устаревшее и проблемное. Возможно, стоит развивать инструментарий автоматических миграций с одной версии языка на другой, линтеры и прочее. Впрочем, и здесь может быть много подводных камней.

  • ABI-related issues. Чтобы передать информацию из одного бинарного модуля в другой, надо либо убедиться, что они собраны идентичными тулчейнами, либо же, если такой возможности нет (интеграция бинарного SDK, third-parties, etc), приходится делать СИ-подобный интерфейс между этими модулями.

  • Разделение на хедер и cpp-файлы. Из-за этого постоянно приходится дублировать часть кода между двумя файлами.

Что советуешь тем, кто начинает изучать С++?

Сам я познакомился с С++ более 15 лет назад до того, как появился так называемый modern C++ (C++11/14 и выше). А с учетом мантры: «Не учите старому С++, учите только современному!», которая раз за разом повторяется на всех уважаемых С++ ресурсах, о тех книгах и журналах, по которым я в свое время изучал С++, лучше попросту забыть, так как новое время требует новых героев :)

Единственное, что я могу однозначно посоветовать – выбирать книги, которые учат именно современному С++. Кстати, следующий большой «перелом» должен наступить уже скоро, как только индустрия перейдет на недавно законченный С++-20 стандарт. В нем появились концепты, рейнжи, корутины и модули. Последние две фичи, думаю, особенно сильно изменят подходы к написанию С++-программ.

А что советуешь изучать тем, кто уже разрабатывает технологии на С++ и хочет постоянно улучшать свою экспертизу в языке?

  • reddit r/cpp. Агрегатор всего интересного, нового, полезного, относящегося к C++. Раньше я бережно собирал RSS-подписки, относящиеся к С++, но накопив больше 30 таких подписок, я попросту перестал успевать их обрабатывать. С reddit такого нет, поскольку благодаря рейтинговой системе в топе «всплывают» действительно самые интересные и значимые посты. Не забывайте читать комментарии пользователей: они отлично дополняют информацию из самого поста.

  • CPP Core Guidelines. Из-за того, что одним из основополагающих столпов С++ является обратная совместимость, существует 100+ способ сделать одно и тоже. Однако некоторые из этих способов уже зарекомендовали себя с хорошей стороны (good practices), а другие - с плохой (bad practices). Конечно, самому наступать на грабли очень полезно, но все же, если имеется возможность, то лучше воспользоваться чужим опытом. Этот чужой многолетний опыт всей индустрии и собран внутри гайдлайнов. Гайдлайны основаны Бьерном Страуструпом и Гербом Саттером, что является дополнительным залогом их качества.

  • CppCon. Youtube-отчет с главной ежегодной С++ конференции. Все видео смотреть не нужно, но рекомендую обратить внимание на выступления Бьерна Страуструпа и Герба Саттера. Они превосходные ораторы и способны зарядить слушателей на то, чтобы продолжать разрабатывать на С++ и получать от этого кайф :)

  • C++ Weekly With Jason Turner. 10-минутные еженедельные видео про разные интересные аспекты языка и стандартной библиотеки. Кратко, интересно, полезно.

  • Блог Modern CPP от Райнера Гримма. Качественные статьи о современном С++, частенько всплывают в том же reddit r/cpp.

Книги:

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

  • C++ Concurrency in Action by Anthony G. Williams. Название говорит само за себя: книга покрывает полный спектр concurrency-related проблем: memory model, multithreading, data races, lock&lock-free и другие.

  • Functional Programming in C++ by Ivan Cukic. Хоть С++ и является мультипарадигменным языком, большинство программ на нем все же разрабатываются в OOP и Generic Programming парадигмах (спасибо темлпейтам). Также в Embedded можно встретить Procedural programming (особенно если разработчики – матерые С-программисты и лишь недавно познакомились с С++). А вот Functional Programming на С++ в реальной практике встречается редко. Тем и уникальна книга Ивана, что он увлекательно показывает, как можно решать известные проблемы в функциональной парадигме с использованием современного С++. После прочтения этой книги начинаешь другими глазами смотреть на, казалось бы, привычный С++-код (спойлер: все хочется переписать! :))

  • C++ Templates: The Complete Guide by David Vandevoorde and Nicolai M. Josuttis. Тысяча страниц «увлекательнейшего» чтения про все возможные закоулки templates metaprogramming. Мне еще не довелось встретить разработчика, который осилил бы этот монументальный труд до конца. И я не исключение :)

Тоже любите C++, как Дима? Приходите работать в нашу команду! Сейчас мы ищем:

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


  1. demoth
    25.01.2022 12:02
    +6

    Ещё из недостатков: низкая скорость компиляции и высокие требования к железу (какой-нибудь хромиум, боюсь, на среднем ноуте невозможно собрать в принципе).


    1. Monsterovich
      25.01.2022 12:53

      Это уже не недостаток языка, а недостаток С++ компилятора. Си был простым и в нём не было ни классов, ни темплейтов, ну и других фич. Когда стали добавлять фичи получилось так что анализировать исходники стало тяжело. Например, при парсинге каждого хедера нужно парсить все includes каждый раз заново. Ну и народ начал извращаться и придумывать precompiled headers (которые, кстати, очень редко где используют) и кеширование .o файлов. Даже в 2022 году си код компилируется быстрее, чем С++ код.


    1. gonzazoid
      25.01.2022 13:21

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


    1. Gordon01
      25.01.2022 13:36
      +1

      Это вы еще раст не собирали))


    1. SergeySib
      25.01.2022 20:35
      +6

      Я какое-то вермя работал в одном кабинете с разработчиками аппаратуры на VHDL и Verilog. Они снисходительно смеялись когда я жаловался на скорость компиляции плюсов.


    1. DistortNeo
      25.01.2022 21:15
      +1

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


      1. demoth
        25.01.2022 22:00
        +2

        Не согласен на счёт всех. Как минимум, есть pascal/delphi и golang, которые компилятся очень быстро. А компиляция крупных проектов на плюсах может занимать длительное время даже с выключенной оптимизацией. А с учётом тьюринг-полных шаблонов она вообще может длиться вечно. :)


        1. DistortNeo
          25.01.2022 22:15

          А есть ли там LTO, whole program optimization? Мне кажется, что нет.

          Например, скорость компиляции MSVC с оптимизациями может быть на порядок, а то и два дольше.


          1. demoth
            25.01.2022 22:49
            +4

            На счёт паскаля не знаю, в го, вроде, нет LTO. Но опять же, плюсы и без оптимизаций дольше компилятся, чем го с оптимизациями. Даже с precompiled headers. Так или иначе, долгая компиляция си++ это недостаток, достойный упоминания.


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


            P.S. И не дай бог поменять бранч… Иначе проект будет пересобираться заново, что займёт уже не 1-2 минуты, а все 20.


            1. Ritan
              26.01.2022 11:36
              +3

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

              И это всё на довольно мощной рабочей станции, не ноут


    1. libroten
      26.01.2022 16:14
      +1

      Ноут за 30к, купленный в 2018 году - полная сборка хромиума летом 2021 заняла почти сутки. 23 часа с копейками :D

      Не знаю, следует ли это записывать в недостатки плюсов (в любом случае, объем кода просто монструозный, на любом языке, кмк, сборка длилась бы десятки часов, не знаю), просто делюсь фан фактом из жизни :D


      1. demoth
        26.01.2022 16:42

        Ну то, что это заняло меньше суток, это весьма обнадёживающе.))


        Я ради интереса собирал kubernetes на raspberry pi 4 с 4гб памяти — чистая сборка занимает 25 минут. Наверняка у него кодовая база гораздо меньше. Допустим, судя по быстрому гуглению оценок cloc, в 25 раз меньше. В этом случае, сборка бы сопоставимого с хромиумом проекта на го заняла бы на малинке 10 часов. :)


        1. kovserg
          26.01.2022 20:33

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


          1. demoth
            26.01.2022 20:47

            Да понятное дело. Такие вещи сравнивать вообще дело неблагодарное, просто интересно было хотя бы грубо прикинуть.


  1. Alexey2005
    25.01.2022 13:58
    +20

    Главная проблема в том, что можно просто наугад выбрать 10 любых C++ проектов на Github, сделать git clone, и все 10 откажутся собираться стандартной связкой ./configure с make.
    Ни в одном другом инструментарии нет таких чудовищных проблем со сборкой проекта, как в тулчейне C++. К каждому проекту всегда прикладывают коротенькую инструкцию, странички этак на четыре, с описанием того, как всё это собирать. А если мы хотим из-под Linux собрать что-то под винду, то сложность сборки вообще устремляется в небеса. Для сборки ряда крупных проектов помимо собственно C++ вам потребуется доустановить Node.js, Perl, Python и Go, потому что процесс сборки использует скрипты на нескольких разных языках одновременно.
    Отдельно доставляет крайне сложная и запутанная система ключей компилятора и линкера, которые взаимодействуют весьма непредсказуемым образом, а их подбор для успешной сборки чужого проекта — отдельный вид шаманства. Не удивлюсь, если окажется, что система ключей там уже давно Тьюринг-полная.


    1. SergeyNovak
      25.01.2022 22:00
      +3

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


  1. sci_nov
    26.01.2022 00:24

    Еще недостаток - трудность и неудобство отладки.

    Выход - не позволять слишком разрастаться С++ проекту. Отвечать только за малую часть проекта. Делать так, чтобы части проекта были бы по возможности независимы. А вообще, всем угодить нельзя!


  1. firehacker
    26.01.2022 09:00
    -6

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

    Менеджера пакетов нет — ах какая катастрофа!!! Ещё через 10 лет программисты, похоже, будут гнобить тот или иной ЯП только за то, что к нему в комплекте не поставляется длинноногая секретарша.

    Забавно, что почти все подчёркнутые им плюсы я считаю минусами, а названным им минусам наоборот рад. Да, когда-то давно в первые пару лет использования C/C++ я тоже злился от разделения на cpp- и h-файлы. Но затем понял фишку, постиг дзен и поражался, какой же я был наивный дурак я был (чисто в духе парадокса Даннинга-Крюгера). А здесь человек 15 лет (!!!) пишет на C++, но видимо до сих пор не вдупляет в то, зачем нужны прототипы, в чем разница между declaration и definition, и почему заголовочные файлы это благо.


    1. Devoter
      26.01.2022 09:15
      +4

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


      1. kovserg
        26.01.2022 09:42
        -2

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


        1. ksbes
          26.01.2022 10:24
          +4

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

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


          1. firehacker
            26.01.2022 13:16
            -2

            Но теперь это — атавизм

            Что поменялось «теперь» по сравнению с временами, когда язык создавался? Люди стали ходить на голове? Число Пи достигло значения 8? Поменялась размерность пространства-времени?

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


            1. DistortNeo
              26.01.2022 14:07
              +1

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

              Что поменялось за это время: выросла кодовая база, число зависимостей. Пока программы состояли из пары десятков файлов и умещались в 640 кб памяти, всё было в порядке. Сейчас программы огромные и сложность компиляции из-за парсинга кучи вырастает экспонециально.


          1. kovserg
            26.01.2022 13:55
            -1

            Не глупые люди придумали скрывать детали реализации.
            Так появились интерфейс и его реализация, которая может меняться не изменяя интерфейса. И интерфейс реализации складывают в заголовки, а реализацию по c/cc/cxx/c++/cpp/… файлам (в идеале).
            Но можно и микроскопом забивать гвозди — вот именно так и появились header only c++ библиотеки.


            1. DistortNeo
              26.01.2022 14:13
              +6

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


              1. kovserg
                26.01.2022 20:35

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


                1. DistortNeo
                  26.01.2022 20:41
                  +1

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


            1. 0xd34df00d
              27.01.2022 05:41
              +3

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


              Зачем это разделение нужно, когда есть нормальные языки с нормальной (относительно костылей на препроцессоре) модульной системой, непонятно.


      1. firehacker
        26.01.2022 13:01

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

        Грубо говоря, когда в обществе долгое время существовал консенсус, что убивать не хорошо, что справедливость это благо, а несправедливость это плохо, и вдруг приходит молодое поколение и внезапно ставит вопрос: «а почему собственно убивать не хорошо?».

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

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

        Так вот, если у вас 190 штук cpp-файлов, и во всех вы используете структуры VECTOR3D, WAVEHDR, ANOTHERSTRUCT, то в каждом cpp-файле придется иметь объявление этих структур. 190 × 3 объявлений.

        И вот здесь, чтобы не плодить 190 копий объявления трёх структур, чтобы уйти от WET-антипаттерна и использовать DRY-принцип, на сцену выходит препроцессорая фича #include, позволяющая повторяющиеся вещи вынести в один файл и избавиться от повторений, за исключением того, что сама директива #include и имя файла, указанное в ней, таки будет повторяться много раз.

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

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

        И дело-то видимо не в том, что человек против идеи includeинга файлов на стадии препроцессинга или не понимает как эти пользоваться. А дело скорее всего в том, что человек против идеи прототипов функций, а также он испорчен и избалован IDE, которая прячет процесс сборки и представляет его пользователю как нечто непрозрачное, и человек перестает думать отдельно о линковке, отдельно о компиляции каждого отдельного исходного файла независимо от остальных, а начинает считать, что есть некий черный ящик под названием «компилятор C++», который как-то там вызывается один раз сразу на весь проект, видит сразу весь проект, обрабатывает весь проект и выплёвывает готовый исполняемый файл, а раз так, ну уж наверное он может в одном cpp-файле увидеть определение структуры или функции, а встретив упоминание чего-то такого в другом cpp-файле, который ни сном ни духом про первый файл, самостоятельно догадаться, что вот именно из того файла сущности здесь и упоминаются.


        1. Grave18
          26.01.2022 14:19
          +8

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


          1. firehacker
            26.01.2022 18:55
            -1

            Понятно. Это что-то вроде желания, будь вы из сферы машиностроения, забыть ненавистное вам оформление проектной документации, а сразу вы тачивать детали «из головы», но чтобы потом по эти деталям проектная документация (чертежи, 3D-модели, спецификации) сами генерировались.

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


            1. DistortNeo
              26.01.2022 20:39
              +1

              Точно так же в других языках программирования есть такие абстракции: interface в C#, например.

              Кстати, с h-файлами есть недостаток: вы не можете объявить только публичную часть класса. Вам придётся в h-файле объявить и все приватные поля, и все приватные методы.


              1. kovserg
                26.01.2022 20:51
                -3

                Совершенно не обязательно

                // a.h
                #pragma once
                struct A {
                  static A* create();
                  virtual int fn()=0;
                  virtual ~A() {}
                };
                

                // a.cpp
                #include "a.h"
                struct A1 : A {
                  int private_variable;
                  int fn() { return private_variable; }
                };
                A* A::create() {
                  A1 *a=new A1();
                  a->private_variable=49;
                  return a;
                }
                


                1. DistortNeo
                  26.01.2022 21:07
                  +1

                  Ну нет, так дело не пойдёт. Вы на пустом месте зачем-то завели виртуальный класс. А слабо в заголочном файле описать именно `A1`?

                  В C#, например, так делать можно:

                  // Interface part
                      
                  partial class A1
                  {
                      public partial int fn();
                  }
                     
                  // Implementation part
                  
                  partial class A1
                  {
                      int private_variable;
                      public partial int fn() { return private_variable; }
                  }


                  1. kovserg
                    26.01.2022 22:30

                    Вы на пустом месте зачем-то завели виртуальный класс

                    Так это как раз недостатки C++, в чистом C такой фигни не было:
                    // a.h
                    typedef struct A A;
                    int  A_init(A** a);
                    void A_done(A **a);
                    int  A_fn(A* a);
                    

                    // a.c
                    #include <stdlib.h>
                    struct A {
                      int private_variable;
                    };
                    int A_init(A** res) {
                      A *a=(A*)malloc(sizeof(A)); if (!a) return 1;
                      a->private_variable=49;
                      *res=a;
                      return 0;
                    }
                    int A_fn(A* a) {
                      return a->private_variable;
                    }
                    void A_done(A **a) {
                      if (*a) { free((void*)*a); *a=0; }
                    }
                    


                    1. DistortNeo
                      27.01.2022 00:32
                      +1

                      Так это как раз недостатки C++

                      Так именно это мы и обсуждаем здесь.


              1. KanuTaH
                26.01.2022 21:29
                +1

                Кстати, с h-файлами есть недостаток: вы не можете объявить только публичную часть класса. Вам придётся в h-файле объявить и все приватные поля, и все приватные методы.

                Потому что в момент, скажем, создания экземпляра класса на стеке компилятор должен знать его размер. Откуда он будет знать размер, если полного описания класса нет? Создать экземпляр класса of incomplete type не получится. Плюс создавать экземпляр класса компилятор должен уметь даже если у него нет ничего, кроме декларации в h-файле и уже собранной библиотеки с методами класса.


                1. DistortNeo
                  26.01.2022 21:48
                  +1

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

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


                  1. KanuTaH
                    26.01.2022 21:51

                    Компилятор вполне может узнать эту информацию из соседних cpp-файлов.

                    Так их вполне может не быть. Разработчик библиотеки свободно может выбрать ее распространение только в бинарном виде (.lib/.a/.dll/.so). А создавать экземпляр класса нужно уметь вне зависимости от способа распространения библиотеки.


                    1. DistortNeo
                      27.01.2022 00:35
                      -1

                      Бинарные библиотеки (.a/.dll/.so) нельзя использовать из C++ напрямую, для этого нужен клей в виде .lib-файла.

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


                      1. KanuTaH
                        27.01.2022 01:59
                        +1

                        Бинарные библиотеки (.a/.dll/.so) нельзя использовать из C++ напрямую, для этого нужен клей в виде .lib-файла.

                        Что, простите? Заголовочный файл (lib.h):

                        #pragma once
                        
                        #include <string>
                        
                        class C
                        {
                        public:
                            C();
                            const std::string &str();
                        private:
                            std::string s;
                        };

                        Реализация (lib.cpp):

                        #include "lib.h"
                        
                        C::C()
                            : s("HELLO FROM LIB")
                        {
                        }
                        
                        const std::string &C::str()
                        {
                            return s;
                        }

                        Вызывающий код (main.cpp):

                        #include <iostream>
                        
                        #include "lib.h"
                        
                        int main()
                        {
                            C c;
                        
                            std::cout << c.str() << std::endl;
                        }

                        Собираем:

                        $ g++ -c -o lib.o lib.cpp
                        $ ar rcs lib.a lib.o
                        $ g++ -o main main.cpp lib.a

                        Запускаем:

                        $ ./main
                        HELLO FROM LIB

                        Никаких проблем.

                        P.S. то же самое с динамической библиотекой:

                        $ g++ -fpic -shared -o lib.so lib.cpp
                        $ g++ -o main main.cpp lib.so
                        $ export LD_LIBRARY_PATH=.; ./main
                        HELLO FROM LIB

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

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


                      1. kovserg
                        27.01.2022 10:11
                        +1

                        А теперь покажите как вы подключаете opengl 4.6


                      1. KanuTaH
                        27.01.2022 11:40
                        +1

                        Зачем? В общем случае тезис "бинарные библиотеки нельзя использовать из C++ напрямую" - ложный, и я это только что наглядно продемонстрировал. I'm not here for your entertainment (C).


                      1. DistortNeo
                        27.01.2022 21:22

                        Что, простите?

                        Вы в своём примере подключаете не бинарный файл, а промежуточное представление (.lib). В отличие от .so/.a/.dll, вы не можете сделать LoadLibrary/dlopen и вызвать код оттуда.

                        P.S. то же самое с динамической библиотекой:

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

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

                        Именно так.


                      1. KanuTaH
                        27.01.2022 22:10

                        Вы в своём примере подключаете не бинарный файл, а промежуточное представление (.lib). В отличие от .so/.a/.dll, вы не можете сделать LoadLibrary/dlopen и вызвать код оттуда.

                        Я подключаю не "промежуточное представление", с чего вы это взяли? Нет там никакого "промежуточного представления". Я подключаю статическую (.a) и динамическую (.so) библиотеку в том виде, в котором они будут распространяться. Для этого достаточно иметь собственно библиотеку (один файл .a или .so) и заголовочный файл, больше ничего не требуется. Динамическая библиотека у меня загружается автоматически при запуске бинаря, а не вручную через dlopen(), это так, но это связано не с наличием или отсутствием некоего "промежуточного представления", а с тем, что dlsym() не совместим с сущностью, называемой в C++ "pointer to member function". Статический метод класса, однако, вызвать таким образом будет вполне возможно. Под виндой, кстати, вполне возможно что удастся вызвать даже и нестатический метод, но я давно не тренировался, так что не уверен на 100%.

                        Именно так.

                        Обратитесь в комитет с proposal.


                      1. DistortNeo
                        27.01.2022 22:16

                        Я подключаю статическую (.a)

                        Да, перепутал. Тогда это то же самое, что и (.lib) — промежуточная форма.

                        Я могу эту .a/.lib подключить из C# или Go?


                      1. KanuTaH
                        27.01.2022 22:24

                        Не знаю, что вы называете "промежуточной формой". .a - это архив из объектных файлов (.o), стандартный формат статической библиотеки в *nix.

                        Я могу эту .a/.lib подключить из C# или Go?

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


                      1. DistortNeo
                        27.01.2022 22:32

                        Не знаю, что вы называете "промежуточной формой"

                        Очевидно, то, что нельзя просто взять и запустить.

                        Объектный модуль (.o) — это не бинарь, это промежуточная форма (compilation unit).

                        .a - это архив из объектных файлов (.o),

                        Ну да, архив из промежуточных форм.

                        Кстати, его конкретное представление стандартом не описывается.


                      1. KanuTaH
                        27.01.2022 22:36

                        Очевидно, то, что нельзя просто взять и запустить.

                        Динамическую библиотеку (.so или .dll) тоже нельзя просто взять и запустить, это тоже "промежуточная форма" или нет? Хочу разобраться в терминологии. В любом случае, для подключения .so .a не нужен, нужен только .so и .h.

                        Кстати, его конкретное представление стандартом не описывается.

                        Конечно, нет. Он же не зависит от конкретного языка.


                      1. DistortNeo
                        27.01.2022 23:08

                        Динамическую библиотеку (.so или .dll) тоже нельзя просто взять и запустить, это тоже "промежуточная форма" или нет?

                        Можно. dlopen, dlsym. Отличие .so/.dll от .out/.exe в том, что в последних есть точка входа для прямого запуска.

                        В любом случае, для подключения .so .a не нужен, нужен только .so и .h.

                        А под виндой для подключения .dll нужен и .lib, и .h.

                        Конечно, нет. Он же не зависит от конкретного языка.

                        Он зависит от принятых соглашений. Например, ничто не мешает создать свои компилятор и линкер со своим представлением .o и .a. Ну не будет оно совместимо с экосистемой gcc/ld, ничего страшного. Просто никто этим не будет пользоваться.


                      1. KanuTaH
                        27.01.2022 23:22

                        Можно. dlopendlsym

                        Видимо, у нас разные понятия о "взять и запустить".

                        А под виндой для подключения .dll нужен и .lib, и .h.

                        Да, под виндой нужна еще дополнительная прослойка в виде import library. Но это не имеет отношения к C++ как таковому, просто в Windows так принято делать implicit linking с DLL. В системах с ELF это не требуется.

                        Он зависит от принятых соглашений. Например, ничто не мешает создать свои компилятор и линкер со своим представлением .o и .a. Ну не будет оно совместимо с экосистемой gcc/ld, ничего страшного. Просто никто этим не будет пользоваться.

                        Под большинством современных *nix .o - это ELF. Такой же, как исполняемые файлы и динамические библиотеки. А то, что "никто не мешает, но никто не будет пользоваться" - это правда. Поэтому у меня и сарказм на тему ваших представлений о том, где C++ компилятор должен находить данные о размере объектов.


                      1. DistortNeo
                        27.01.2022 23:41

                        Видимо, у нас разные понятия о "взять и запустить".

                        Да. Взять и запустить код из файла. В случае .lib/.a для этого обязательно нужен определённый компилятор, понимающий формат этого файла. В случае .dll/.so — API операционной системы. Кстати, .out/.exe тоже через вызовы функций надо запускать (CreateProcess, exec).

                        Но это не имеет отношения к C++ как таковому,

                        Да, не имеет. Стандарт C++ этого не описывает, поэтому и получается подобный зоопарк.

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

                        Да, обратная совместимость, которая вставляет палки в колёса. Но ничто не мешало просто расширить метаданные, не поломав совместимость: подцепляем из C — размера типа не видим, подцепляем из C++ — видим.


                      1. KanuTaH
                        27.01.2022 23:54

                        В случае .lib/.a для этого обязательно нужен определённый компилятор, понимающий формат этого файла.

                        Как я уже выше сказал, под большинством *nix .a - это архив объектных файлов (.o) стандартного формата (ELF), не зависящего от компилятора. ld способен собирать из них исполняемый файл без посторонней помощи, ничего не зная о компиляторе, который их сделал. При большом желании их можно даже запустить вручную.

                        Да, не имеет. Стандарт C++ этого не описывает, поэтому и получается подобный зоопарк.

                        Так dll'ки же не только C++ может создавать. Почему то, как их создавать, должно быть описано в стандарте? Стандарт ортогонален конкретному формату исполняемых файлов и динамических библиотек на конкретной платформе.

                        Да, обратная совместимость, которая вставляет палки в колёса.

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


                      1. DistortNeo
                        28.01.2022 00:01

                        Как я уже выше сказал, под большинством *nix .a - это архив объектных файлов (.o) стандартного формата (ELF), не зависящего от компилятора.

                        Ну да, так сложились звёзды, что компилятор C++ в Unix-подобных ОС генерит файлы стандартного формата.

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

                        Стандарт не может описывать, как хранить, но может описывать, что должно храниться в этих файлах.

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

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


                      1. KanuTaH
                        28.01.2022 00:13

                        Стандарт не может описывать, как хранить, но может описывать, что должно храниться в этих файлах.

                        Ммм, а зачем? Чтобы заведомо вынуждать генерить файлы нестандартного и несовместимого ни с чем формата, с которыми не сможет работать стандартный компоновщик? И все ради того, чтобы .h-файл не прикладывать? И что бы мы выгадали? Вместо .so и .h был бы .so и какой-нибудь .pkg (а еще веселее - И .so И .h И .pkg)? Шило на мыло.

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

                        Проблемы есть везде, просто везде они разные. Такова жизнь.


            1. 0xd34df00d
              27.01.2022 05:43
              +2

              А я сразу начинаю с типов функций и разбитию по модулям. Зачем для этого .h и препроцессор?


        1. DistortNeo
          26.01.2022 14:30
          +2

          А главным образом в том, что каждый cpp-файл можно компилировать отдельно и в отрыве от всех остальных

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

          Например, скомпилировать один файл на 10000 строк займёт значительно меньше времени, чем 100 файлов по 100 строк. Поэтому приходится тесно связанные вещи пихать в один файл внавал.

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

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

          Так вот, если у вас 190 штук cpp-файлов, и во всех вы используете структуры VECTOR3D, WAVEHDR, ANOTHERSTRUCT, то в каждом cpp-файле придется иметь объявление этих структур. 190 × 3 объявлений.

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

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

          А дело скорее всего в том, что человек против идеи прототипов функций

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


          1. firehacker
            26.01.2022 20:43

            Написал не менее длинный ответ на этот коммент, и в последний момент он канул в небытие. Слава новому WYSIWYG-редактору Хабра!

            Второй раз набирать нет желания: я вообще-то с телефона набирал, так как лежу с температурой 39, палец и так отваливается. Может быть потом


        1. Gordon01
          26.01.2022 15:48
          +3

          И вот здесь, чтобы не плодить 190 копий объявления трёх структур, чтобы уйти от WET-антипаттерна и использовать DRY-принцип, на сцену выходит препроцессорая фича #include, позволяющая повторяющиеся вещи вынести в один файл и избавиться от повторений, за исключением того, что сама директива #include и имя файла, указанное в ней, таки будет повторяться много раз.

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

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

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

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

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

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


          1. KanuTaH
            26.01.2022 19:54
            +2

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

            М-м-м. Это как в Java/Kotlin, где ты должен сначала трудолюбиво разложить файлы по папочкам, потом в начале каждого файла не забыть трудолюбиво написать package com.horns.and.hooves.api.shit.auth? Крутяк. А потом, если Horns & Hooves потребуется переименовать в Hooves & Horns, процесс надо будет повторить - ну или понадеяться на какую-нибудь киллер фичу очередной мега IDE по решению проблемы, которой бы не было, если бы в язык не была добавлена столь по-дурацки спроектированная "особенность". Запах современного языка настолько силен, что глаза вытекают.

            Всего этого в с++ нет, не предвидится в ближайшие несколько лет и можно только надеяться

            что в C++ такого не будет никогда.


            1. 0xd34df00d
              27.01.2022 05:45
              +1

              Это как в Java/Kotlin, где ты должен сначала трудолюбиво разложить файлы по папочкам, потом в начале каждого файла не забыть трудолюбиво написать package com.horns.and.hooves.api.shit.auth?

              Чем это отличается от include guard'ов в плюсах и необходимости заинклюдить нужный хедер в соответствующем cpp? Ну, кроме того, что в плюсах компилятор за их правильностью и уникальностью не следит, а #pragma once — решение нестандартное и не везде рекомендуемое?


              1. KanuTaH
                27.01.2022 06:53
                +1

                Даже если и ничем (хотя, конечно, есть чем - после перемещения в другой каталог хедеры обычно править не требуется) - в чем "модерновость"-то тогда? :)


                1. 0xd34df00d
                  27.01.2022 07:01
                  +1

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

                  Ну вообще-то требуется — нужно обновить include guard.


                  в чем "модерновость"-то тогда?

                  В отсутствии препроцессора.


                  1. KanuTaH
                    27.01.2022 11:32

                    Ну вообще-то требуется — нужно обновить include guard.

                    Не нужно :)

                    В отсутствии прпрепроцессора.

                    Утверждение, что наличие (ну или отсутствие) препроцессора как-то доказывает отсталость (или модерновость), несколько голословное на мой вкус :) Отдаёт эдакой вкусовщиной, знаете ли.


                    1. 0xd34df00d
                      27.01.2022 23:08
                      +3

                      Как не нужно, если в гарде так или иначе должен быть закодирован путь к хедеру на случай двух хедеров с одинаковым именем в разных папках?


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


                      1. KanuTaH
                        27.01.2022 23:25

                        Как не нужно, если в гарде так или иначе должен быть закодирован путь к хедеру на случай двух хедеров с одинаковым именем в разных папках?

                        #pragma once :) Да, это нестандартно, это имеет свои минусы, но де-факто это есть.

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

                        Знаете, как говорят - не смотрите на это как на проблему, смотрите, как на возможность :)


                      1. 0xd34df00d
                        27.01.2022 23:42
                        +1

                        pragma once :) Да, это нестандартно, это имеет свои минусы, но де-факто это есть.

                        Де-факто есть и плагины, которые за вас все нужные объявления модуля сгенерируют или обновят.


                        А вот с компаниями, где #pragma once (про который, к слову, я выше тоже написал) запрещён, я знаком, и даже в некоторых из них работал.


                        Знаете, как говорят — не смотрите на это как на проблему, смотрите, как на возможность :)

                        И возможность чего это? Ну, кроме job security и попить чай, пока код компилируется.


                      1. KanuTaH
                        27.01.2022 23:56

                        И возможность чего это?

                        Возможность осуществить тупую подстановку текста (и игнорировать часть текста в зависимости от текста, который был тупо подставлен перед этим), конечно же! В Java/Kotlin такое возможно? Нет :)


                      1. 0xd34df00d
                        28.01.2022 00:01
                        +1

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

                        А зачем это?


                        В Java/Kotlin такое возможно? Нет :)

                        Ими, к счастью, мир не ограничивается.


                      1. KanuTaH
                        28.01.2022 00:04
                        +1

                        А зачем это?

                        Пригодится.

                        Ими, к счастью, мир не ограничивается.

                        Хаскель уже можно записывать в отсталые языки с препроцессором, или пока погодить? :)


                      1. 0xd34df00d
                        28.01.2022 00:13
                        +1

                        Пригодится.

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


                        Хаскель уже можно записывать в отсталые языки с препроцессором, или пока погодить? :)

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


                        Собственно, из всех моих хаскель-проектов я препроцессор изредка юзаю только в библиотеках, и только для поддержки разных API-несовместимых версий зависимостей.


              1. technic93
                28.01.2022 01:57

                Нужно использовать `#pragma once`. Иначе противоречие: мы поддерживаем какие-то старые подходы в языке, тем самым потокаем системам которые древнее мамонтов, а потом требуем чего-то современного?


  1. CyberRonin
    26.01.2022 14:19

    Поэтому существуют многочисленные «лагеря несогласных» с «де-факто стандартным» CMake: Make/Ninja/Meson/Build2 и другие. До сих пор нет по-настоящему удобной интеграции с IDE (та же VisualStudio).

    Довелось поработать с CMake. Но относительно недавно (чуть более полугода назад) наткнулся на https://premake.github.io/. На мой взгляд очень простая, но в то же время очень мощная система генерации make-файлов. Очень интересно узнать, использует ли кто-то данный инструмент у себя в средних и больших проектах ?


    1. Gordon01
      26.01.2022 15:38

      Друзья используют, отзывы очень хорошие.


  1. Akiyamka
    26.01.2022 14:19

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


    1. KanuTaH
      26.01.2022 15:15
      +1

      Эк вы безапелляционно рассуждаете. Прямо как в известном анекдоте:

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

      — Ты уверен в этом, папа?

      — Абсолютно, сынок!

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

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


      1. Gordon01
        26.01.2022 15:49

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

        А просветите тогда, как можно модно и молодежно использовать #include ?


        1. KanuTaH
          26.01.2022 16:00
          +1

          Я же написал выше:

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

          Использование #include не требует от каждого из этих компиляторов уметь парсить код всех остальных для того, чтобы выделить экспортируемые сущности, это раз (это уж не говоря о случае, когда библиотека доступна в бинарном виде). Во-вторых, в заголовочном файле допустима условная компиляция, которая позволяет "спрятать" те интерфейсы, которые компилятор на требуемом языке может не понять, и "показать" те, которые он понять может. Это как минимум. Вот в C++20 добавляют модули, но они не годятся для такого случая, так же, как не годятся и ObjC-специфичные вещи типа@import. А #include годится.


  1. MrSung
    26.01.2022 16:54
    +4

    У c++ есть один недостаток - я запарился его учить.


    1. kovserg
      26.01.2022 20:41
      +3

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


  1. teemour
    27.01.2022 01:48
    -1

    Напоминает древний культ


  1. middle
    27.01.2022 06:33
    -1

    У цитате Страуструпа:

    Garbage is generated by C++ programmers instead.


  1. x2v0
    27.01.2022 12:04

    Обычно, я не читаю книги по программированию - предпочитаю ковыряться в чужом коде.
    Но, эта книга стала исключением - "C++ concurrency in action"
    Я вернулся (после C#, Kotlin) к C++ и обнаружил, что в "мое отсутствие" в нем всё кардинально изменилось.