LINUX.ORG.RU

C++ — туда и обратно, или зачем нужен Boost

 , , ,


3

5

Мой предыдущий тред в Development собрал самое большое число ответов аж с сентября, то есть за последние 9 месяцев, и это лишний раз подтверждает упадок этого форума. Полагаю, кто-то должен это изменить, и поэтому мы с тобой, ЛОР, поговорим сегодня про C++.

Начиная с C++26 вместо std::function вводится пачка новых классов: std::copyable_function, std::move_only_function (доступна с C++23) и std::function_ref. Что же не так с оригинальным std::function, ты можешь спросить? А вот что:

#include <functional>
#include <print>

struct call_me {
    int x = 0;
    void operator()() {
        std::print("x was {}\n", x++);
    }
};

int main() {
    const std::function<void()> f = call_me{};
    f();
}

Несмотря на то, что переменная f объявлена константной (люблю оксюмороны!), у неё есть внутреннее состояние и оно меняется при вызовах. Компилятор это без проблем хавает.

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

Небольшой список фич, которые были придуманы, оказались не нужны/бесполезны/вредны и выкинуты:

  • Известный vector<bool>, живущий издревле в STL и про который все говорят, что его надо избегать. Частично заменяется std::bitset.
  • std::auto_ptr. Бесполезен, ломает контейнеры, выкинут на помойку в C++17.
  • Указание исключений у функции в формате throw(X, Y). Так же выкинуто в C++17.
  • std::iterator объявили устаревшим в C++17, собираются удалить в C++26.
  • std::aligned_storage и std::aligned_union добавлены в C++11, объявлены устаревшими в C++23, скоро удалят.
  • Ключевое слово register удалено в C++17, хотя всё ещё доступно в Си.
  • std::get_temporary_buffer и std::raw_storage_iterator удалены в C++20.
  • Потрясающее по эпичности фиаско с интерфейсом для сборщиков мусора. std::declare_reachable сотоварищи были добавлены в C++11. Выяснилось, что сборщики мусора для C++ писать либо никто не умеет, либо никто не хочет, поэтому в C++23 это всё удалили и сделали вид, что ничего не было.
  • Абсолютное безумие вокруг концептов, модулей и поддержки сети. Предложения одобряли, вновь отклоняли, переделывали, и по итогу теми же модулями до сих пор никто не пользуется.
  • Сопрограммы (coroutines). В том виде, в котором они есть в C++, это просто ужас. Достаточно того, что корутины требуют выделения памяти из кучи во время работы, а значит вообще не подходят для случаях, когда требуется серьёзная производительность. Например, в любом коде, требующим работы в реальном времени и не позволяющем делать системные вызовы.

Просто лютый трешак, который никто подчищать пока не собирается:

  • std::regex – лютый тормоз, рекомендуется не использовать.
  • Мертворождённый std::simd, добавленный в C++26 и уже с ходу не нужный вообще никому, потому что код с std::simd в два-три раза тормознее чем со сторонними библиотеками, просто голыми интринсиками, и даже медленнее чем просто цикл for.
  • std::async. Дескруктор ждёт завершения асинка и поэтому может залочить весь код тебе. Наконец починили в C++26, но эта штука была сломана 15 лет.
  • Отвратительно спроектированный <iostream>. Его наконец можно выкинуть и использовать std::print, но не все про это знают.
  • Абсолютно тормозные контейнеры map, set, unordered_map. Вместо первого можно использовать flat_map из C++23. Контейнеры в стандартной библиотеке Rust (BTreeMap) и другие реализации B-Tree Map их обгоняют по производительности, но тем не менее в C++ выбирают убогий дефолт.

Решения многих из этих проблем существуют в Boost и сделаны там гораздо лучше. Но в то же время возникает важный вопрос: зачем вообще нужен настолько плохо спроектированный язык, где каждое следующее поколение инженеров, работающих над ним, отменяет решения предыдущих, а код под новые стандарты часто нужно переписывать если не с нуля, то очень близко к тому? Даже процесс разработки Rust с его поехавшими клоунами в юбках на этом фоне выглядит адекватным.

В общем, всё печально, ЛОР. Такие дела.



Последнее исправление: CrX (всего исправлений: 3)
Ответ на: комментарий от zurg

Никто, никогда не забывает ставить copy,

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

бывают типы которые нельзя копировать

Частный случай, 0,00001%.

Внимание вопрос: зачем язык как дефолтную конструкцию рассматривает 0,00001%, а в остальных 99,9(9)% погромизд должен копипастить семантический мусор?

r--r--r--
()
Ответ на: комментарий от yorshka

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

В продолжающей работать программе.

В Rust же почти никто паники не перехватывает и, соответственно, последствия от паники в drop куда мягче.

Программа тупо падает, да.

r--r--r--
()
Ответ на: комментарий от eao197

В C++ использование try{} catch() {} является чуть ли не повсеместным

Похоже, все зависит от того, кто куда и как смотрит.

Если ты работаешь на гугель и весь код собирается с -fno-exceptions, вопросов конечно нет. Но это редкость. В среднем, в проектах, с которыми я работал, исключения достаточно часто использовались как часть control flow, особенно в более старом коде (до C++11).

yorshka
() автор топика
Ответ на: комментарий от yorshka

В среднем, в проектах, с которыми я работал, исключения достаточно часто использовались как часть control flow,

Я специально поискал catch в своем проекте. Всего лишь несколько десятков вхождений на 37KLOC кода.

eao197 ★★★★★
()
Ответ на: комментарий от yorshka

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

Я даже на java такого не видел.

r--r--r--
()
Ответ на: комментарий от eao197

Весь этот набор банальностей не отвечает на заданные мной вопросы.

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

Возможно, это сообщение тебе тоже покажется банальным.

Darfin
()
Ответ на: комментарий от eao197

В среднем, в проектах, с которыми я работал, исключения достаточно часто использовались как часть control flow,

Я специально поискал catch в своем проекте. Всего лишь несколько десятков вхождений на 37KLOC кода.

Всё, что это значит, это что мы работаем над разными проектами. Только и всего.

Тем не менее, в Rust при написании реализации drop программисту не нужно думать что какая-то из вызванных функций запаникует, если это явно не является частью API (например, .unwrap(), за который надо по рукам бить), потому что паника является аварийной ситуацией. В случае с C++, стоит рассчитывать, что любая посторонняя функция может кинуть исключение.

TL;DR не надо сравнивать панику и исключения в C++. Это разные механизмы для разных целей.

yorshka
() автор топика
Ответ на: комментарий от Darfin

Возможно, это сообщение тебе тоже покажется банальным.

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

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

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

И поэтому буквально каждый веб-фреймворк имеет готовое middleware для перехвата и обработки паник, ага.

r--r--r--
()
Ответ на: комментарий от yorshka

Всё, что это значит, это что мы работаем над разными проектами. Только и всего.

Я, собственно, сразу об этом и сказал:

все зависит от того, кто куда и как смотрит.


TL;DR не надо сравнивать панику и исключения в C++.

Я не сравниваю панику с исключениями. Я сравниваю количество знаний, которые нужны в C++ для написания деструктора, с количеством знаний, которые нужны в Rust-е для написания Drop::drop.

eao197 ★★★★★
()
Ответ на: комментарий от r--r--r--

И в чём же фундаментальная разница?

Документацию почитай, хотя бы:

https://doc.rust-lang.org/reference/panic.html

Например, в том, что бесконечный цикл при панике – вполне валидная реализация.

yorshka
() автор топика
Ответ на: комментарий от eao197

Я сравниваю количество знаний, которые нужны в C++ для написания деструктора, с количеством знаний, которые нужны в Rust-е для написания Drop::drop.

Окей. В C++ этих знаний требуется больше.

yorshka
() автор топика
Ответ на: комментарий от yorshka

бесконечный цикл при панике – вполне валидная реализация.

Но это не разница между throw и panic!(). Это разница между крестами и растом.

Давай я переформулирую вопрос.

Дано:

Пишем обработчик http-запроса, в ходе которого фетчим предзагруженные данные в бд. В ходе обработки, у нас возможны три сценария ошибок:

  1. Ошибка на вводе / ввыводе с БД
  2. Ошибка структуры БД
  3. В БД нет pre seed данных

API на все операции корректно возвращает Result / Option.

Вопрос:

Будешь ли ты делать unwrap()?

r--r--r--
()
Ответ на: комментарий от r--r--r--

https://godbolt.org/z/j1rc31h8d я буду использовать С++, потомучто в нём есть наследование, а Раст не серьезный язык, даже с такой тулой, факт заключается в том, что С просуществовал в еще более худшем состоянии, когда Раст не осилел наследование, но в тот же момент осилил борова

anonymous
()
Ответ на: комментарий от eao197

получается одна из нюансов С++ и Раста

https://godbolt.org/z/abdY6MTKz С++

https://godbolt.org/z/ersj5hdT4 Раст

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

anonymous
()
Ответ на: комментарий от zurg

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

а по тому что мол костылинг, есть же вроде std::optional

anonymous
()
Ответ на: комментарий от r--r--r--

Но это не разница между throw и panic!(). Это разница между крестами и растом.

Я даже не знаю, как вот эту строчку прокомментировать.

Будешь ли ты делать unwrap()?

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

yorshka
() автор топика
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от r--r--r--

Будешь ли ты делать unwrap()?

Конечно же нет.

А что ты будешь делать? Вот вернул тебе сиквель запрос ошибку. Твои действия?

Очень странный вопрос, чувак.

Встречный вопрос: вернул тебе read() в сишной проге ошибку. Что ты будешь делать? Падать в кору?

yorshka
() автор топика
Ответ на: комментарий от yorshka

а тут нюансы есть, предположим байты не потерялись от С, которые пришли в Раст, где-то читал, что байты могут потеряться, типо С кидает строку, а Раст на приёме потерял что-то. А в событийной модели красивее, кд, события и прочее, наверно это и есть control flow.

anonymous
()
Ответ на: комментарий от yorshka

вернул тебе read() в сишной проге ошибку. Что ты будешь делать?

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

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

Ввернул тебе сиквель запрос ошибку. Твои действия?

r--r--r--
()
Ответ на: комментарий от r--r--r--

Теперь к моему «очень странному» вопросу, который, как я догадываюсь, описывает что-то очень странное и вообще не возможное:

Ввернул тебе сиквель запрос ошибку. Твои действия?

Ну вот ровно так же и сделаю:

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

Только вместо ручного сишного if(err != 0) return err; в Rust есть Result и оператор ?. А в остальном суть та же.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Ну вот ровно так же и сделаю:

Что ты сделаешь-то, я не понял нихера. Ты пишешь код обработчика. Никакого контроля над кодом по стеку выше у тебя нет. API обработчика требует вернуть из функции объект с ответом. Как вариант, можешь вернуть экземлпяр impl Responder / Writable. Твой err клиенту в браузере нахер не упёрся.

в Rust есть Result

Тебе ещё Option обрабатывать надо.

и оператор ?.

Который чем тебе поможет?

Ты, я так чувствую, с растом только по срачам на лоре знаком?

r--r--r--
()
Ответ на: комментарий от r--r--r--

Никакого контроля над кодом по стеку выше у тебя нет. API обработчика требует вернуть из функции объект с ответом. Как вариант, можешь вернуть экземлпяр impl Responder / Writable. Твой err клиенту в браузере нахер не упёрся.

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

Тебе ещё Option обрабатывать надо.

И в чём проблема?

Ты, я так чувствую, с растом только по срачам на лоре знаком?

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

yorshka
() автор топика
Ответ на: комментарий от yorshka

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

anonymous
()
Ответ на: комментарий от anonymous

с вэб мордой можно пойти по пути как Ipython, предположим есть утилита которая создаёт шаблон проекта для С++, она может создать шаблон, добавлять модуль, добавлять библиотеку в зависимость, собирать и запускать, очевидно она на разметке какой-то, можно эту разметку докрутить до HTML, тоесть состояние и команды проекта рисовать в вэбэ например )…

anonymous
()
Ответ на: комментарий от BruteForce

Корректность и эффективность использования без необходимости гадать - «а не провиснут ли итераторы/ссылки из-за того что внутри что-то перевыделится». В растовых - клиенту достаточно ориентироваться только на внешний интерфейс. Плюсовые типы так не умеют, в смысле, эффективно локализовать UB-шные эффекты. Чего ни как не поймут мракобесы вроде чувака выше с шизофазией - «лол, кек, азаза, в друсте нету списков».

zurg ★★
()
Ответ на: комментарий от anonymous
<?xml version="1.0"?>
<package>
    <meta name="GameEngine1" version="1.0.0" type="application" />
    <compiler path="clang++-20" standard="23" optimization="2">
        <stdlib value="libc++" />
        <std_module_path value="/usr/lib/llvm-20/share/libc++/v1/std.cppm" />
        <flags>
            <flag value="-Wall" />
            <flag value="-Wextra" />
            <flag value="-Wno-unused-command-line-argument" />
        </flags>
        <links />
        <lsp>
            <arg value="      -std=c++23," />
            <arg value="      -stdlib=libc++," />
            <arg value="      -Iinclude," />
            <arg value="      -I/usr/lib/llvm-20/share/libc++/v1," />
            <arg value="      -fmodule-file=std=../bin/std.pcm," />
            <arg value="      -fmodule-file=Math=../bin/Math.pcm," />
        </lsp>
        <lib>
            <links name="assimp" />
        </lib>
    </compiler>
    <structure>
        <dir path="src" type="sources" />
        <dir path="include" type="headers" />
        <dir path="bin" type="output" />
        <dir path="assets" type="resources" />
        <dir path="modules" type="modules" />
        <dir path="ThirdParty" type="dep" />
        <dir path="dep_dirs_comp" type="dep" />
        <dir path="modules" type="modules" />
    </structure>
    <dependencies>
        <lib name="assimp" url="..." version="latest" cmake_flags="" />
    </dependencies>
</package>

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

anonymous
()
Ответ на: комментарий от BruteForce

Где я такое подаю? Но в практическом смысле таки победа - код получился намного прямее и читабельней сишной бажной лапши

zurg ★★
()
Ответ на: комментарий от ckotctvo

у меня есть хитрожопое дерево и хитрожопый список.

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

zurg ★★
()
Ответ на: комментарий от zurg

переписал в лоб на хэшмапах вообще не задумываясь об оптимизации и … обошёл свою «оптимизированную» сишную версию процентов на 20-30 (точно не помню, но порядок такой).

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

anonymous
()
Ответ на: комментарий от zurg

а в расте нельзя локализовать разыменование unsafe *const T который смотрит на С, получается, на стадии отладки будут тоже баги, пока пишешь свои ответные интерфейсы к С интерфейсам если стремиться к нулевой зависимости, чтобы не качалось лишнего.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от yorshka

Ты формулируешь дико абстрактные вопросы,

Как имплементация конкретных трейтов для salvo, actix, rocket или axum может быть "дико абстрактной", норкоман?

контекст к которым есть только у тебя в голове

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

единственный вариант тут – записать в лог и вернуть 500.

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

Правильный ответ на вопросы выше - во всех случаях нужно кинуть панику, и 500 в одной точке кода отдаст клиенту перехватывающий панику middleware.

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

r--r--r--
()
Ответ на: комментарий от zurg

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

boost::intrusive_rbtree и его дружбаны не позволяют этого делать из коробки, их надо дорабатывать. Но это хотя бы можно, идем в доку в раздел «Node algorithms with custom NodeTraits» и делаем там свою реализацию итератора, который можно подвинуть после изменения адреса. PROFIT!

а stlные какахи так не могут, еще и память аллоцируют постоянно.

ckotctvo
()
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария