Древнее зло

Язык С++ по-настоящему стар. Казалось бы это должно идти ему на пользу, как хорошему вину. Но этому мешает обратная совместимость. Хорошая идея, если бы она работала...

Давайте просто честно признаемся: ни один из стандартов не был обратно совместим. ВСЕГДА реальные проекты требовали миграции и адаптации не то что под новую версию стандарта, но даже под новую версию компилятора. Чем больше проект, тем больше усилий, и порой фатальных. Уверен, среди читающих найдется человек, у которого на работе все еще 98й стандарт С++.

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

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

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

#include

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

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

Все это заставляет жонглировать инклюдами и forward declaration'ами, из-за циклических зависимостей и времени компиляции. И даже если MSVS смог схавать каким-то образом твой код, то придет clang у унизит тебя. Попробуйте-ка сделать пару шаблонных классов, ссылающихся друг на друга, в двух разных хидерах! А если их станет больше двух - добро пожаловать в ад.

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

Время компиляции

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

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

Ошибки

Выдавать абсолютную тарабарщину на элементарные вещи при ошибках - это просто классика. Тут я поругаю самих разработчиков, использующих С++ - ну вы то чего терпите? Как вообще мы столько лет уживаемся с этим фактом и еще не начали бунд!!1

Ты забыл указать конструктор, оператор или просто опечатался - лови тонны нечитаемой херни. Обязательно нужно все перечислить, не упустить ни одной детали, хотя в 99% случаев они не нужны и тебе нужна краткая выжимка.

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

Разработчиков IDE здесь тоже сложно похвалить, ведь буквально ничего нельзя сделать с этим... Эй, jetbrains, ловите идею: фолдить дефолтные специализации шаблонов в "...", чтобы по клику они разворачивались - они никому не нужны в 99% случаев. Включить подсветку синтаксиса, чтобы было удобно читать. А еще можно наделать шаблонов ошибок и пытаться эвристиками делать дедукцию реальных причин ошибки. Ну или совсем по-современному, отдать это ChatGPT для генерации краткой выжимки.

Строки

Строка в С++ не то чтобы строка: "abrakadabra" может быть и выглядит как строка, но в языке С++ это указатель на массив.

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

Однако код"abrakadabra" == "abrakadabra"вернет false, ведь это два разных массива, их адреса конечно же не равны. Или попробуем приплюсовать одну строку к другой: "abra" + "kadabra" Компилятор спросит вас, зачем ты пытаешься суммировать два указателя? И правда зачем...

Было бы не все так печально, если бы строки не нужны были так часто. Окей, нормальные строки конечно же есть в языке - std::string. Их относительно легко использовать, особенно если использовать литералы.

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

Стандартная библиотека

Продолжим про строки, в разрезе стандартной std::string. Какие операции можно делать со строками? Базовые конкатенацию, поиск и т.п. С этим нет проблем.

Но есть пачка стандартных и распространенных методов для строк, которые без труда можно использовать в других языках: split, starts/ends_with и подобные. Части из них в стандартной библиотеке нет. В целом, очень многого в стандартной библиотеке нет, даже из базового набора.

На мой взгляд стандартная библиотека на то и стандартная, что она должна покрывать максимум удобства, быть эталоном API. Но в С++ на API стандартной библиотеки не ругался только ленивый: то того нет, то этого. Реализация тоже вызывает вопросы, поэтому некоторые делают свои варианты (напр. EASTL).

Лично для меня еще существенным минусом является вынесение общих функций наружу, вместо использования их как функций‑членов класса. Мне, например, гораздо проще и очевиднее вызвать myVector.find_if() через точку, чем вызывать ее как внешнюю функцию с передачей итераторов внутрь:

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

  • необходимость передачи .begin()/.end() в большинстве случаев. Это удлиняет и засоряет синтаксис: std::find_if(myVector.begin(), myVector.end(), [] (auto& x) { ... }) . Достаточно более краткой записи myVector.find_if([] (auto& x) { ... }) . Зачем каждый раз передавать в функцию этот дефолтный набор begin/end()?

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

Открывая буквально любую книгу по программированию, примерно на первой странице можно увидеть что-то вроде "не используй тарабарщину для имен переменных". Названия икеевской мебели звучат и то лучше, чем имена переменных внутри кода стандартной либы: _myIt, _myLast. Когда я вижу префикс "my", сразу вспоминаются MyClass, MyVariable из туториалов по программированию... Не такого уровня ожидаешь от стандартной библиотеки.

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

Комитет стандарта

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

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

В итоге самое важное вылизывается годами, не дай боже накосячить и выпустить что-то кривое. Ой блин, погодите, а кривое все равно выходит. Сразу вспоминается какой-нибудь auto_ptr<>. Уверен найдутся люди которые его выкорчевывали из кода и бухтели "обратная совместимость обратная совместимость...".

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

Отсутствие очевидных фич

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

Мне повезло попробовать разные языки, особенно C#, в больших продакшн проектах. У меня наберется несколько простых, но супер-удобных фич из C#, которые можно было бы перенести в С++ вообще без проблем. Уверен у тех, кто пользовался и другими языками, тоже наберется несколько фич, которые легко встроятся в текущий С++ довольно просто.

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

  • properties, или члены класса, оборачивающие setter и getter в виде обычной переменной. Иногда гораздо удобнее оперировать со значением как с обычной переменной, например, для каких-нибудь векторов: object.position = object.position + other_object.position + delta;. При этом работать не со значением напрямую, а через setter/getter. В моем примере это важно, т.к. изменение позиции объекта как правило порождает перерасчет матриц трансформации. И выглядит это гораздо понятнее и более читаемо, чем вот такое: object.SetPosition(object.GetPosition() + other_object.GetPosition() + delta);

  • extension methods. Это возможность вне класса добавить какие-то методы API к нему. То есть у меня есть какой-то класс, допустим, в библиотеке. Я не могу поменять его код. Но мне хотелось бы внести в него какое-то новое API, чисто ради удобства. В C# вне класса можно написать статичный метод, который принимает this MyClassType, и в котором доступно его оригинальное API. На уровне синтаксиса такой метод считается членом класса и его можно вызвать через (.) точку. Если бы такая возможность была бы, то и проблем с API стандартной библиотеки не было бы, давно бы уже сделал все нужные экстеншены

Лямды

Спасибо что они есть, раньше ведь не было. Даже вспоминать страшно... Опять же, попробуем сравнить с C#. Допустим, нужно найти первый элемент коллекции, удовлетворяющий предикату-лямде. Посмотрим как это выглядит в C#:

collection.Find(obj => obj.myTag == tag);

лаконично. Давайте взглянем на это в С++

collection.Find(..

Ах блин ну чего это я

std::find_if(myVec.begin(), myVec.end(), [&tag](const GameObject& obj) { return obj.myTag == tag; });

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

Хорошим тоном считается не использовать короткие [&] и [=], мало ли че там "захватится". Но по факту почти везде нужна ссылка... Поэтому перечисляем весь скоуп захвата. Хорошо что в примере одна переменная, в реальности как правило там целая пачка имен.

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

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

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

Смарт-поинтеры

Указатели это же базовая вещь языка? В каких-то языках есть GC, в каких-то вшиты в язык shared/weak умные указатели. Но в С++ это пришито сбоку и реализовано как отдельные классы. Они не включены в сам синтаксис, из-за чего возникают проблемы.

Например, вот вы используете std::make_shared<> для более эффективной аллокации, чтобы счетчик был перед объектом для лучшей кеш-утилизации. А теперь попробуй взять шареный указатель на this из конструктора. Не получится, счетчика то еще нет, он прокинется в объект только после конструктора.

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

Приходится городить фабрики и другие костыли, чтобы обойти эти сайд-эффекты. Это порождает вторичные сайд-эффекты, например IDE не понимает что вызов std::make_shared<> - это вызов конструктора по факту, и не может показать тебе список аргументов.

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

SFINAE

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

Итак, SFINAE - это "substitution failure is not an error ". По-русски - ошибка при подстановке типа шаблона не является ошибкой. То есть компилятор пытается специализировать шаблон каким-то типом, в процессе получает ошибку компиляции, и такой "окей, значит так и должно быть, ошибку игнорим и идем дальше".

"И как же через это можно сделать добрую часть функционала?" - спросят некоторые из вас. А я о чем! Как блин через это можно было пропихнуть столько важных вещей?

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

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

if (typeid(MyClass).has_method(hello()))
    myClass.hello();

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

Хехе, давайте посмотрим как это работает в реальном С++:

template<class T, class = std::void_t<>>
struct has_hello: std::false_type { };

template<class T>
struct has_hello<T, std::void_t<decltype(&T::hello)>>: std::true_type { };

...

if constexpr (has_hello<MyClass>::value))
    myClass.hello();

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

Часть с if'ом еще более-менее понятна и лаконична. Даже удобно что if обозначен constexpr , явно обозначающий статичность условия. Не очень понятно откуда взялся ::value, но в целом это еще нормальная часть.

Ну а вот выше это вот магия как бы всех устраивает? Нет никаких вопросов, выглядит как понятный код?

Да, тут применяется SFINAE. Если у класса нет метода hello(), то подстановка его в шаблон в void_t<decltype(&T::hello)> дает ошибку, т.к.decltype(&T::hello) дает ошибку, т.к. типа указателя на несуществующую функцию не существует. В итоге компилятор молча отбрасывает специализацию has_hello, которая наследуется от std::true_type, а вот другая специализация, наследующаяся от std::false_type компилируется без проблем и has_hello<T>::value является константой false.

Ну вот насколько нужно быть упоротым чтобы это на серьезно использовать? Всё мета-программирование С++ сделано через SFINAE. При этом, уверен, некоторые type trait классы все-таки реализованы на уровне компилятора.

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

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

Ты не платишь за то, чего не используешь

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

Рассмотрим пример - dynamic_cast<> и RTTI (runtime type information). Это отключаемая фича в С++, но по-дефолту она включена и многими используется. Многими программистами С++ она воспринимается как бесплатная, однако при включении RTTI постоянно происходит скрытая работа в рантайме.

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

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

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

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

IDE

Немного не честно, но все же еще относительно недавно (пару-тройку лет назад) типичные IDE для C++ выглядели как сарай в сравнении с другими IDE для других языков. До смешного, даже одна и та же IDE MSVS совсем разный зверь для C++ и C#.

Сейчас есть jetbrains, плагины для MSVS (отчасти тоже от jetbrains), с которыми можно жить относительно комфортно. Однако до сих пор, сравнивая работу в IDE для С++ и других языков, C++ больше про страдание.

Голый MSVS 2022 без плагинов по функционалу примерно такой же, как и MSVS 6.0 от 1998 года. Да, я утрирую, но все же в той старой версии было практически все, что есть и сегодня. Единственная мажорная фича, что приходит в голову - это подсветка ошибок в коде без компиляции. Основной функционал работы я получаю из плагинов - адекватный быстрый поиск, переименование, инклюды и т.п.

Для mac os еще недавно вообще был только xCode. Кто пользовался, тот поймет. Если MSVS не дотягивает до других IDE других языков, то xCode - это буквально чуть выше плинтуса. Он тормозит, не умеет вообще ничего, а на больших проектах отваливается и подсветка синтаксиса, и автодополнение. Справедливости ради, тулзы профилирования в xCode - одни из лучших.

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

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

Послесловие

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

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

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


  1. ilriv
    20.05.2024 08:40
    +8

    Переходите на C++Builder))


    1. IvanPetrof
      20.05.2024 08:40
      +1

      Но но.. Попрошу птичку нашу не обижать. Некоторые с него и не уходили))


      1. Ken-Sei
        20.05.2024 08:40
        +2

        Эх! Если бы на нём работали либы от Visual C++, то я бы с C++ Buiilder и не уходил никуда. Но, увы, нужная либа часто под Visual C++, а C++ Buiilder не поддерживается. :-(


  1. bfDeveloper
    20.05.2024 08:40
    +12

    Простите, а это в каком году написано? Наезжать на инклюды и SFINAE после выхода C++20 странно. Я понимаю, что не все проекты могут себе позволить перейти на него, сам на 17м сижу, но это же не претензия к развитию языка.

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

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


    1. anz Автор
      20.05.2024 08:40

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

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


      1. bfDeveloper
        20.05.2024 08:40
        +3

        SFINAE никуда не денется, он всё ещё используется и работает. Концепты не полностью заменяют, а решают 90% задач коротко и просто. Точно так же constexpr не решает все задачи шаблонов, только выделяет популярное подмножество и предлагает понятный способ решения.


      1. Kahelman
        20.05.2024 08:40
        +6

        Вы не любите кошек? Вы просто не умеете их готовить.

        по поводу setter/getter который автор пытается использовать для работу с элементами вектора -вообще-то есть перегрузка операторов, которая в вашем конкретном примере сократит синтаксис.

        Пользоваться или нет -решать вам.

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

        If defined module_included …

        Это вроде азбука объявлений header файлов в С/С++.


        1. durnoy
          20.05.2024 08:40
          +2

          Предположу, что речь была не про include guard (это само собой), а про парсенье инклудов в разных файлах в одном проекте. Если без precompiled headers, то одно и то же разбирается и компилируется снова и снова.


          1. y_mur
            20.05.2024 08:40
            +1

            Недавно экспериментировал с precompiled headers в MSVC 2022 и пришёл к выводу что не зря их теперь там по умолчанию отключили. Сейчас студия предкомпилирует всё подряд и довольно неплохо отслеживает что и когда нужно перекомпилировать, а что нет. Ускорения от включения precompiled headers не заметил.

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


        1. anz Автор
          20.05.2024 08:40

          сорри, не понял про setter/getter для вектора... setter/getter у меня упоминается в пропертях, которые удобно их заворачивают, это не только к векторам и контейнерам относится, а ко всему

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


    1. pooqpooq
      20.05.2024 08:40

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

      Да ладно проекты, тут ещё системы сборки едва подтягиваются. cmake только в 3.28 сделал модули не-экспериментальными, как обмодуливать всякие там boost и qt — непонятно, и так далее.


    1. buldo
      20.05.2024 08:40
      +1

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

      На нашем небольшом open-source проекте код собирается внутри os, которую предоставляет вендор: raspberry pi OS, ubuntu для Jetson, debian для radxa. И там нафига не последние версии gcc или cmake.

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


      1. Arenoros
        20.05.2024 08:40

        а какие альтернативы то на этих же платформах?


        1. buldo
          20.05.2024 08:40

          Если говорить про "как использовать свежий компилятор для debian в "полу-embedded" - то только собирать свой тулчейн для кросс компиляции. Или собирать свежий компилятор на самой sbc.

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


          1. Arenoros
            20.05.2024 08:40

            не, я про альтернативу плюсам, в сборке тулчейна особо то проблем нет, crosstool-ng с этим очень сильно помогает.


            1. buldo
              20.05.2024 08:40

              Я всё хочу раст попробовать, как бы попсово это не звучало.

              С другой стороны, я умею писать быстрый код и на шарпах, так что ещё не скоро до раста дойду


    1. IgorsVolohovs2003
      20.05.2024 08:40

      Согласен, статья старая


  1. rsashka
    20.05.2024 08:40
    +5

    Поставил плюс автору за старательность и минус за саму статью. Все, что вы написали неверно из-за одного момента. Вы не правильно понимаете:

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

    Это следует понимать, не как "накладные расходы на RTTI довольно маленькие, но они есть, от этого никуда не деться", а "если вам не требуется RTTI, то опция -fno-rtti полностью убирает даже минимальные накладные расходы и ненужный вам функционал".

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

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


    1. pooqpooq
      20.05.2024 08:40
      +3

      если вам не требуется RTTI, то опция -fno-rtti полностью убирает даже минимальные накладные расходы и ненужный вам функционал

      А стандарт точно подразумевает эту опцию?

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

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


      1. rsashka
        20.05.2024 08:40

        Стандрарт подразумевает возможность откючения RTTI, а по какой опции, это делали реализации контретного компилятора, со всеми остальными прдложениями идите в комитет по стандартизации :-)


  1. DarthVictor
    20.05.2024 08:40
    +8

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

    В каких областях сейчас популярен С++ и какие у него в них были конкуренты в последние годы?
    Мне вспоминаются такие:

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

    • Интерфейсы высокопроизводительных десктопных приложений. То что осталось на С++: браузеры, редакторы фото и видео с огромным циклом разработки. Что-то переписывать слишком долго переписывать, что-то вроде браузеров особо и не на что. Недавно появился Rust, но число разработчиков на нём несопоставимо, наработок меньше.

    • HFT - конкурентов особо нет.

    Итого. У С++ долго не было особых конкурентов в его нише компилируемых языков без сборщика мусора и zero-cost абстракций. У большинства других языков конкуренты были. У Java был С#, а затем ещё и другие языки внутри экосистемы JVM. У Python - PHP, Ruby. Даже у формально безальтернативного JS были компилируемые в него языки в качестве конкурентов. Ну Rust появился, но у него и близко пока нет экосистемы С++.

    Любопытно посмотерь поменятся ли что-то лет через 5-10 после появления Rust, а также всяких Zig, Carbon и прочих Mojo.


    1. follow39
      20.05.2024 08:40
      +22

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


      1. DarthVictor
        20.05.2024 08:40
        +3

        Да, спасибо что напомнили. Я ещё СУБД забыл, и там конкурентов тоже не было.


      1. vadimr
        20.05.2024 08:40
        +5

        В эмбеддед в основном пишут на обычном Си.


        1. UranusExplorer
          20.05.2024 08:40
          +6

          Смотря в каком, эмбеддед очень разный бывает. Под bare metal - да, под RTOS - я бы сказал уже 50/50, а под Embedded Linux C++ встречается повсеместно.


          1. SIISII
            20.05.2024 08:40
            +3

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


        1. Yuri0128
          20.05.2024 08:40
          +6

          Все-ж уже больше на ++. На старые контроллеры - да, на C в основном.


          1. vvzvlad
            20.05.2024 08:40

            Ну там часто смесь. Какие-то фичи из плюсов используются часто.


      1. placidity_master
        20.05.2024 08:40

        Rust ?....


    1. Samhuawei
      20.05.2024 08:40

      Интерфейсы высокопроизводительных десктопных приложений.

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

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


      1. DarthVictor
        20.05.2024 08:40
        +5

        Вообще я имел в виду всякие Photoshop, Blender, AutoCad, 3Ds Max, Adobe Premier.


      1. wataru
        20.05.2024 08:40
        +7

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


        1. MiyuHogosha
          20.05.2024 08:40

          И при этом на питоне это будет один поток, следовательно еще и тормоза )


      1. rkfg
        20.05.2024 08:40
        +2

        Биткоин с 2013 майнят только на асиках, потому что видюхи проигрывают им на порядки по производительности и энергоэффективности. Что ни говори, а C++ развивается быстрее хотя бы «биткоин-критиков», уже неплохо!


    1. Newbilius
      20.05.2024 08:40
      +5

      Конкурентов С++ почти нет, особенно для игровых движков.

      Но авторы игр в большинстве своём пишут не игровой движок, а игровую логику для этого движка ;-) В тех же играх на Unity основной язык разработки именно С#


    1. pooqpooq
      20.05.2024 08:40
      +2

      HFT - конкурентов особо нет.

      На самом деле C++ довольно плох для HFT в абсолютных цифрах, ибо всё равно даёт довольно много оверхеда и сложен для не-плюсовиков (а квантов, например) и на нём тяжело получать даже эту самую производительность даже с оверхедом (привет грозьдям темплейтов). Здесь написание своих собственных предметных языков (с компилятором на чём-то ML-подобном, например) заруливает всех.


      1. KillRussia
        20.05.2024 08:40

        А в чём проблема не использовать темплейты, раз вам они не подходят?


        1. JediPhilosopher
          20.05.2024 08:40
          +1

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


        1. pooqpooq
          20.05.2024 08:40

          В том, что без них медленнее. Ну, если всё ещё оставаться в рамках C++.


    1. DaneSoul
      20.05.2024 08:40
      +4

      Любопытно посмотерь поменятся ли что-то лет через 5-10 после появления Rust

      <...> первая стабильная версия (1.0) вышла 15 мая 2015 года <...> (Вики).
      То есть ему уже 9 лет.


      1. DarthVictor
        20.05.2024 08:40

        Первая стабильная версия Golang вышла в 2009, но никто на нём всерьёз не писал до примерно 2015г. Версия 1.0 у Python — 1994.

        Субьективно с очень хорошим пиаром как у Golang и C на языке начинают массово писать в прод лет так через 4-5. А обычно — лет через 8-10.


        1. eao197
          20.05.2024 08:40

          Первая стабильная версия Golang вышла в 2009, но никто на нём всерьёз не писал до примерно 2015г.

          В 2009-ом Гугл просто предъявил Golang миру, релиз версии 1.0 у Golang-а состоялся в марте 2012-го.
          Собственно, про Rust так же было известно задолго до релиза версии 1.0 в мае 2015-го.

          Если верить Wikipedia, то Docker появился в 2013-ом, а в 2014-ом там заменили LXC на написанный на Go libcontainer:

          Docker debuted to the public in Santa Clara at PyCon in 2013.[48] It was released as open-source in March 2013.[21] At the time, it used LXC as its default execution environment. One year later, with the release of version 0.9, Docker replaced LXC with its own component, libcontainer, which was written in the Go programming language.

          Так что с "никто" и "всерьёз не" получается как с совой на глобус.

          Ну и добавьте к дате релиза Go 1.0 девять лет и оцените насколько сильно он проник в мейнстрим к тому времени. И сравните с проникновением в этот же мейнстрим Rust-а на данный момент. Как раз 9 лет с релиза версии 1.0.


          1. qwerty19106
            20.05.2024 08:40
            +1

            Тут смотря что считать мейнстримом. Например в области микроконтроллеров без библиотек вендоров (HAL) редко кто пишет. И пару лет назад они начали появляться на Rust.
            Но кроме того на Rust есть стандартное API HAL (https://github.com/rust-embedded/embedded-hal), чего за все цать лет не удалось создать в других языках.

            Фактически под все микроконтроллеры есть библиотеки на С, сильно меньше половины на С++, и сейчас примерно столько же на Rust. Я считаю что это уже мейнстрим.


    1. mak_trefa
      20.05.2024 08:40

      "без сборщика мусора" - есть полумеры в виде ARC (obj-c, swift, vala). есть @nogc в D


      1. MiyuHogosha
        20.05.2024 08:40

        Самое смешное в стандарте предполагался недосборщик мусора. Раньше. Его выкинули, т.к. ни один компилятор не смог это реализовать