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

я то как раз смог потому что пишу на С++.

Нет, не можешь. Контейнеры, сравнимые по надёжности и качеству с растовыми, написать на плюсах невозможно, включая и эти пресловутые списки. Например, даже банальный пользовательский вектор уже стало возможно без UB написать? Так что нет у тебя нихрена. И, это, осильте уже что список это такой высокоуровневый интерфейс с абстрактными операциями получить следующий/предыдущий елемент (и двумя особыми в начале/конце), а все эти укозатели, индексы, ключи мапов или чёрт лысый - детали РЕАЛИЗАЦИИ. И ссылки не для построения структур данных, а позволяют (как и весь раст со всеми этими его типами) использовать контейнеры так чтобы программа в целом не сгнивала и не разваливалась как это обычно происходит в сишках-плюсишках.

удаляем владеющую ссылку

Ох тыж… ведь в самом деле не понимаешь что несёшь, а мнение имеешь. Владеющая ссылка - это оксюморон, просто вот по определению.

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

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

https://godbolt.org/z/r569eYe68 например так наверно, даже не знаю, а я бы хотел, чтобы Раст был читаемый, поддерживал здоровые приёмы наследования на уровне компилятора, и код от С как-то не улетал чтоли в каких-то моментах, да и некоторые контейнеры по типо str, String, при таком подходе Раст были контейнерами, а не структурки на коленке выведенные в лапшу методов… Тоже на любителя получается кстати.

anonymous
()
Ответ на: комментарий от zurg
pub struct FlatBVHNode {
    pub min: Vec3f,
    pub max: Vec3f,

    pub left_child: u32,
    pub right_child: u32,
    pub brush_index: Option<usize>,
}

pub struct BVH {
    pub nodes: Vec<FlatBVHNode>,
}

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

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

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

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

Да ты очумел, это года 3-4 назад было, я поискал - не нашёл, может удалили уже. Но факт довольно известный - на достаточно сложных алгоритмах и современных архитектурах человеки плохо определяют где реальные узкие места. И те чуваки из статьи которые прототипировали на расте в режиме - «сунем .clone() потом причешем» - при измерении обнаружили что разница в производительности не существенна. Например, такой факт: процы сейчас копируют из памяти, минимум, целыми кэшлайнами и нет разницы скопировать примитив или блоб на десятки байт, и это может оказаться выгоднее чем косвенное дёрганьё этого блоба, увеличивая вероятность кэшмисов.

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

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

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

Простите? А какие критерии?

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

П.С. Но это, конечно, прикол: хэшмапа обогнала список в задаче «вычисления графов»

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

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

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

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

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

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

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

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

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

А зачем граф «хранить»? С ним работать надо, а работать быстрее всего с adjacency list.

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

Хранить граф в виде (разряжённой) матрицы

«Граф в виде (разряжённой) матрицы» — это что-то новенькое. Есть adjacency list (который может быть и массивом, и связным списком), есть adjacency matrix, можно рёбра хранить в хэш-таблице, можно ещё много всего начленовертить. Всё зависит от задачи. Но называть adjacency list матрицей…

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

«Граф в виде (разряжённой) матрицы» — это что-то новенькое

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

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

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

От списка рёбер с парой вершин до матрица смежности / идентичности и т.д.

Т.е. и список смежности, и матрица смежности — это матрицы? Я хотел сказать «учите терминологию», но вы выдали буквальный русский перевод английских терминов и отрицаете, что ваши русские термины соответствуют английским.

Для собственно операций над графами как правило матричное представление и используют.

Значит, все операции над графами, как правило, требуют O(|V|^2) памяти?

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 1)
Ответ на: комментарий от yorshka

Контейнеры в стандартной библиотеке Rust (BTreeMap)

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

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

Т.е. и список смежности, и матрица смежности — это матрицы?

Нет. "Список смежности" (чтобы это ни было) - это я хз. что. А матрица смежности - это квадратная матрица, да.

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

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

Значит, все операции над графами,

Это слишком полное обобщение. Я бы так обще не обобщал.

P.S. А "adjacency matrix" - это хеш, да.

r--r--r--
()
Последнее исправление: r--r--r-- (всего исправлений: 2)
Ответ на: комментарий от r--r--r--

Окей, термины «список смежности» и «матрица смежности» описывают:

а) абстрактные математические объекты в вакууме (as in, «Пусть A — матрица смежности ориентированного графа без петель; тогда (A^n)[i,j] равно количеству путей i -> j длины n»);

б) конкретные структуры данных с конкретной асимптотикой (as in, «Если мы используем матрицу смежности для представления графа, то алгоритм Флойда-Уоршелла работает за O(|V|^3)»)?

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

https://godbolt.org/z/Ka7hc8oKK

типо тогда встаёт вопрос, если есть такое зачем нам это если есть вектор? если в дереве можно вектор заюзать со слотами ради скорости? а если так, то на расте можно сразу вектором воспользоваться зачем нужен список, если в дереве будет специфичная ситуация тоже с вектором и слотами, и получается список тоже будет со слотами ) тоесть ну бред, потомучто вопрос появляется после появления в окружении первой реализации вектора )

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

*если ноды и данные для списка не складываются рядом

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

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

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

Матрица смежности графа […] — это квадратная целочисленная матрица $A$ размера $n \times n$, в которой значение элемента $a_{i,j}$ равно числу рёбер из $i$-й вершины графа в $j$-ю вершину.

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

So, both?

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 1)
Ответ на: комментарий от shdown

Adjacency matrix — это bool[N][N]. В ней ты очень быстро можешь проверить, есть ли ребро между i и j. Но она требует O(N^2) памяти. Ты это имел в виду?

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

См. например сюда: https://libeigen.gitlab.io/eigen/docs-5.0/SparseMatrix_8h_source.html

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

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

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

б) конкретные структуры данных с конкретной асимптотикой
So, both?

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

она требует хранения по одному биту данных для каждого элемента

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

Реализация в мелочах отличается, но алгоритмы работы сохраняются.

r--r--r--
()

Теперь самое интересное, что тянется из С

#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();
}

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

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

помню читал книжку Expert Programming Peter van Linden, там авторы топили за портянки конст и обясняли эффекты, чото такое видел )

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

получается поэтому добавили новое правило как я понимаю, в нашем времени, где с const std::move_only_function<void()> f = call_me{}; уже другое поведение не адрес консистентен(получается это старое поведение), а то где учитывается конст по новому типо или как должно было быть наверно, ну я так понял.

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

тогда не знаю, если честно делать без абстракций на С анси с float[16], акууратно прописать это хитрым проходом, то прирост есть за счет обхода Gather/Scater, но этот отказ от абстракций не удобен или тянет на библиотеку и еще кучу проверок типо поддержки процессором операций. интересно вообще

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

Ты гарантируешь, что каждая структурка во всех трейтах имеет Copy? 75%? 50% ?

Нет, я ж не шизофреник такие проценты высчитывать. Как минимум, потому что бывают типы которые нельзя копировать. А вот что действительно шизофазия это выдумывать такие высосанные из пальца «проблемы». Никто, никогда не забывает ставить copy, я такого не припомню. Представь, я спрошу сколько процентов плюсеров забудут напейсать все эти: const, default, delete, explicit, override, final, плюс ещё прорва специфичных компиляторных атрибутов, не забыть сделать виртуальным деструктор, не выбрасывать из них исключений, вообще бесконечный дроч с исключениями и их сейфити, конструктуры - тоже сломаны, и… дохера ещё всего в такой степени, что в нормальную прогерскую голову это не помещается и что-то где-то обязательно выстреливает.

Секта скопцов, второе пришествие.

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

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

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

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

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

А что в Rust-е паники уже отменили? Как и трайт Drop? Или в Drop::drop паниковать теперь можно без каких-либо опасений?

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

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

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

Паника – это аналог вызова abort().

Тогда почему при выбросе паники вызываются drop-ы?

Это не гарантированное поведение. Например, вот вполне корректный обработчик паники, который никакие drop не вызывает:

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    loop {}
}

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

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

Это не гарантированное поведение.

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

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

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

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

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

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

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

Только если он хочет делать что-то необычное. В среднем, нет.

И если он реализует Drop, то должен ли он в Drop::drop держать в уме, что drop может быть вызван при раскрутке стека из-за паники?

Что значит «держать в уме здесь»? Что drop сам не должен паниковать? Это желательно, но даже если drop при панике вызовет панику, это всё равно приведёт к аварийному завершению программы, как и обычная паника.

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

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

Только если он хочет делать что-то необычное. В среднем, нет.

Собственно, как и в C++.

Что значит «держать в уме здесь»?

Помнить про рекомендации лучших собаководов.

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

Собственно, как и в C++ по отношению к исключениям из деструкторов. Типа эти простые правила напрягают тов.@zurg в C++, но в Rust-е для него это уже норм.

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

Собственно, как и в C++ по отношению к исключениям из деструкторов. Типа эти простые правила напрягают тов.@zurg в C++, но в Rust-е для него это уже норм.

Не совсем. В C++ использование try{} catch() {} является чуть ли не повсеместным, и утечка ресурсов из-за исключения в конструкторе или десктрукторе может обернуться неприятными сюрпризами. В Rust же почти никто паники не перехватывает и, соответственно, последствия от паники в drop куда мягче.

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