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)
Ответ на: комментарий от anonymous

Ну справедливо, луч от камеры — это вообще более честный подход, потому что камера у тебя и так уже держит view-projection матрицу, и ты просто инвертируешь то, что и так посчитано, вместо того чтобы городить отдельный unproject для UI-слоя.

Луч от игрока — это то же самое, только camera offset уже включён в позицию персонажа, и получается, что ты стреляешь не из глаз абстрактной камеры, а из глаз конкретного Васи с мечом, что для gameplay-логики честнее, особенно если у тебя есть third-person камера, которая от игрока в трёх метрах позади и слегка сверху, и тебе плевать откуда рендерится картинка, тебе важно откуда физически летит урон.

Про двух-проекционность — да, это стандартная штука, screen-space UI рисуется отдельным ортографическим проходом поверх перспективного, и они действительно не обязаны делить один raycast. Просто дело в том, что «нарисовать двух-проекционно» и «протестировать клик двух-проекционно» — это два разных пайплайна, которые легко рассинхронизировать: рендер у тебя честно рисует UI поверх сцены, а хит-тест по привычке лезет сначала в мировые координаты, потому что кто-то полгода назад один раз написал raycast для gameplay и все input-события с тех пор идут через него по инерции.

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

так самое простое, что можно придумать, вы это и писали раннее, сначала обходим УИ - 2д, и else тут пошел счет перенос в мировые координаты, из (камеры, сдвиги, глаза, и прочее ).

именно поэтому я написал, что сначала мы городим коробки, пускай и 1 кусок сцены, без стриминга, типо сделали квадрат коробок, сделали коллизии. и вот тут иногда просто не хочется, писать как есть, и приходиться писать УИ систему, и думать как это в архитектуру прокидывать, так вот из-за прохода луча и системы УИ, УИ просто красиво пишется отдельно, но как бы текущий узел мира, сидит на else проходе, типо никуда не попали попадаем в мир, так я о том и написал, в этой ситуации не важно какая игра 2д/3д. нам важно в УИ прокинуть - тоесть оповестить УИ в каком узле мира мы находимся.

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

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

Что касается модулей: их отсутствие в Си — далеко не ключевая причина появления Rust. В C++20 модули уже есть, но проблемы безопасности памяти это не решило. Если добавить модули в Си, он всё так же останется языком с ручным управлением памятью и регулярными уязвимостями, так что потребность в безопасном системном языке никуда бы не делась.

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

Ну вот именно, ты же сам и сформулировал главный вывод, просто ещё не назвал его словом: это же Chain of Responsibility в чистом виде, только вместо паттерна из книжки у тебя просто if/else с двумя ветками, и это нормально — паттерн не перестаёт быть паттерном от того, что ты его написал по наитию, а не назвал по имени (a rose by any other name). Сначала UI-слой говорит «мимо», потом эстафета уходит в мир — и вот этот «мимо» и есть весь контракт, который тебе нужен, никакого стриминга, никакого ECS с нодами, просто честное else.

И вот тут ты нащупал ту самую вещь, из-за которой людям «не хочется писать как есть» — потому что UI-система формально не часть мировой архитектуры, но architecturally обязана знать, что мир вообще существует, хотя бы чтобы уметь сказать «я тебя не съела, лети дальше». И вот это вот «прокинуть узел мира в UI» — это как раз та точка, где 90% инди-движков начинают городить event bus, потому что тыкать UI напрямую в GameState руками — грязно, а формального контракта между слоями ещё нет, вот и получается observer, garbage под шумок, и три способа подписаться на одно и то же событие к концу проекта.

Но смысл у тебя верный: не важно 2D это или мерзкое 3D, потому что и там и там задача одна — «проверили UI, не попали, теперь сообщи миру, в каком узле мы оказались». Разница 2D/3D тут вообще не в логике прохода, а только в том, чем ты меряешь пересечение — AABB против ray-vs-sphere, суть от этого не меняется, меняется только математика внутри одной и той же ветки else.

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

это в срезе инструментов, наследование это инструмент, боров это инструмент.

С упрощает отводит от ассемблера, не давая наследование, - первый инструмент

С++ упрощает взаимодействие классов и отводит от С,

а боров решает возможно вопросы безопасности и скорость, но уводит от С и С++

модули тоже инструмент в С/С++, потомучто инклуд может напряч процессор. Получается это всё на плоскости инструментов по взаимодействию с чем-то в ПК.

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

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

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

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

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

оптимизации лексического анализа

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

деконструкция самой методологии

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

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

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

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

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

Ну BVH для этого — элегантно как идея, потому что ты по сути говоришь «у меня два больших bounding volume, один называется UI, другой называется Мир, и я просто проверяю контракт до того, как лезть внутрь» — это честно, это быстро, и для двух верхних узлов это работает как часы. Проблема начинается ровно там, где ты сам и нащупал: скролл.

Скролл ломает BVH именно потому, что BVH рассчитан на то, что геометрия либо статична, либо перестраивается редко и дорого — ты же строишь дерево заранее, а потом только спускаешься по нему. А скролл — это контент, который живёт за пределами своего собственного bounding box'а, то есть у тебя есть видимый прямоугольник контейнера, а внутри него — контент, который формально снаружи, просто со clip-mask, и вот это «формально снаружи, но визуально внутри» убивает всю честность BVH, потому что твои bounds врут по определению. Ты либо делаешь bounds по полному контенту — и тогда хит-тест начинает видеть элементы, которые юзер не видит, либо по видимому rect'у — и тогда ты руками досчитываешь offset при каждом запросе, что уже не BVH, это BVH с костылём.

А парент-чилд тут выигрывает именно потому, что он не про геометрию — он про ownership и порядок обхода. Скролл в парент-чилд — это просто нода с clip-rect и scroll-offset, которая знает, что все её дети живут в её локальном пространстве, и при хит-тесте просто трансформирует точку клика в своё пространство перед тем, как спросить детей. Никакого rebuild, никаких честных bounds — просто рекурсивный спуск с трансформацией контекста, и это работает для любой вложенности, хоть скролл внутри скролла внутри попапа внутри скролла.

Так что твой BVH хорош на верхнем уровне — UI vs Мир, один пас, один контракт — а дальше внутри UI честнее именно парент-чилд, потому что UI это не про пространственную близость, это про иерархию владения, а это разные вопросы, просто оба выглядят как дерево.

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

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

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

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

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

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

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

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

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

Что-то вчера ещё было потешно, а сегодня уже не потешно, а как-то грустненько, тяжелеет на душе…(

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

если в методе с симд убрать выравнивание, то мы переходим в плоскость, что for медленнее чем std::simd, можно еще включить наносекунды начать со 100 и потом на 1000000 прогонять. у меня симд от 0.1 до 0.3 быстрее чем фор сейчас.

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

а в таком стиле пазл 2д удобно будет сделать с интерфейсом, 1 кнопочка в центре начать, и по входу в пазл сверху красивая панелька - любая, помимо самого пазла? как бы вы в таком стиле пазл исполнили какой-нибудь простенький без opengl через sdl2 хотябы.

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

ну кстати если написать на крейте sdl2 терминал-емулятор, тоже garbage будет, даже похлеще как мне видится, потомучто в случае с Rect от библиотеки раст, там тоже знать надо, pty то придётся тоже писать на расте с С не помню будет мусорить, но там цифры поэпичнее кстати. Самый топ терминал сегодня это терминал на свинге, ничего не фонит, по сравнению с Растом и С++…

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

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

Контекста мало, не совсем понятно в чём проблема. Что за порядковый номер? Индекс? Абстрактный порядковый номер, вроде, тем и хорош что не зависит от реализации

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

Так derive как раз и означает что бойлерплейт генерирует компилятор, ты точно понимаешь смысл этого термина бойлерплейт?

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

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

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

Прогаммист ручками херачит копипаст ты точно понимаешь смысл этого термина бойлерплейт?

А ты?

только две структуры с явным Copy на десятки структур

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

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

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

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

на вводных 1к задачках - роадмап в .md, или 300 или 500 там от 0 до топ задач если сгенерировать вы этот нюанс увидите, чем так частично вникать. только модель выберите, которая рассуждает…

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

Уровень 1: Основы синтаксиса (1–20)

Типы данных, переменные, циклы и функции.

* Hello Rust:** Выведите строку в консоль. +

* Калькулятор:** Сложение, вычитание, умножение и деление двух чисел. +

* Четное/нечетное:** Проверка числа на четность. +

* Температура:** Конвертер из Цельсия в Фаренгейт. +

* Факториал:** Вычисление через цикл for. +

* Фибоначчи:** Генерация первых N чисел. +

* Таблица умножения:** Вывод таблицы для числа X. +

* Реверс строки:** Выведите строку задом наперед. +

* Сумма массива:** Сложите все элементы числового массива. +

* Максимум:** Найдите самое большое число в списке. +

* FizzBuzz:** Классическая задача (3 — Fizz, 5 — Buzz). +

* Простое число:** Проверка числа на простоту. +

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

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

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

можно еще по новому посмотреть на старые задачки, типо найти полиндром через HashMap(nth) и из HashMap выбрать ответы. Это не нормальное программирование поэтому это просто из-за того, что просто у Раста есть лапша открытых методов, делать так не целесообразно, это просто прикольно.

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