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

ChkTag пока существует только на бумаге

Пройди дальше по ссылкам, балда.

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

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

Так какая модель процессора-то?

Intel Core Ultra 200S, линейка Xeon 6 и т.д. Но, повторю ещё раз, pointer tagging используется буквально повсеместно. Включая линуксовое ядро.

Аналогичная штука на ARM64. Прямо сегодня в твоём телефоне, чувак, если он не старше пары лет. В моём пикселе точно есть.

https://www.kernel.org/doc/html/v5.15/arm64/memory-tagging-extension.html

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

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

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

универсального контейнера

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

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

И что, популярен стал этот твой LAM? И чего ты съехал на LAM и Pointer Tagging? Как это связано с поломкой обратной совместимости ABI? Наоборот, не сделали новое несовместимое ABI, а сделали обратно совместимое послабление касательно битовой репрезентации указателя.

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

Т.е. если у вас был вектор, который умещался в 32 байта, а затем вы хотите увеличить его объем вдвое, то скорее всего вам выдадут новый блок в 64 байта из другого пула. А когда с 64 будете увеличивать до 128 – то новый блок из совсем другого пула. И realloc здесь может помогать разве что пока у вас в векторе 1-2-3 значения совсем маленького размера (по 1-2 байта на каждое).

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

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

А ещё можно придумать новое ABI, где указатели передаются вместе с границами!

И чего ты съехал на LAM и Pointer Tagging? Как это связано с поломкой обратной совместимости ABI?

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

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

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

Если, например, использовать rigtorp::MPMCQueue, то она при переполнении будет лочиться на атомарном спинлоке, просто сводя на нет всю асинхронность. Это конечно плохо, но при должном упорстве вообще любой логгер захлебнется. Универсального решения тут нет, можно разве что поиграть с размером очереди в зависимости от ожидаемой нагрузки. По части падений: гарантированной записи последнего сообщения при падении в асинхронный логгер добиться сложно, хотя на практике часто получается вывести хвост очереди (и даже стектрейс) из обработчиков сигналов / std::terminate. Да, внутри обработчика есть ограничения на безопасные действия, но некоторые трюки всё же работают.

Ну или предложите свою схему, которая по вашему мнению лучше.

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

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

Разве что в идеальных условиях однопоточной программы, которая не делает ничего кроме заполнения единственного std::vector<int>. Уже на std::vector<std::string> эта картина мира рухнет. Не говоря уже про многопоточку. И не затрагивая случаев, когда для всего приложения в качестве штатного аллокатора задействовали mimalloc/tcmalloc/jemalloc.

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

ChkTag пока существует только на бумаге

Пройди дальше по ссылкам, балда.

Так какая модель процессора-то?

Intel Core Ultra 200S,

Но эта модель процессоров НЕ поддерживает ChkTag. В даташите есть всё, что угодно - от теневого стэка до шифрования памяти, но ChkTag в нём нет.

Стало быть, ты нагло наврал.

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

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

А что ей ещё делать? Семечки щелкать? не, ну если на каждый чих обращаться к куче - конечно смысл потеряется.
Да и многопоиочная программа наиболее вероятно будет спать в других потоках. Случай когда 2 потока одновременно что-то аллрцируют не такой уж и частый,

на std::vector<std::string>

Отличный пример, как делать не нужно, как минимум по этой причине. Заводите список и не страдайте фигнёй. Ладно бы ещё требовалось непрерывное последовательное расположение элементов, но тут в этом смысла нет...

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

А что ей ещё делать?

Простите, а мы сейчас говорим о сферической программе в вакууме?

Или о чем-то практическом? Например, о микросервисе, написанном на C++ и общающемся с внешним миром через REST.

Да и многопоиочная программа наиболее вероятно будет спать в других потоках.

Ох, блин.

Случай когда 2 потока одновременно что-то аллрцируют не такой уж и частый,

Ох, блин. Два раза.

Заводите список и не страдайте фигнёй.

В этих наших интернетиках пишут, что std::list практически всегда хуже, чем std::vector. Тот же Страуструп об этом чуть ли не с конца 1990-х говорит.

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

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

Например, о микросервисе, написанном на C++ и общающемся с внешним миром через REST.

И он боольшую часть времени должен заниматься аллокацией объектов? Или всё-таки обработкой запросов?

std::list

Никогда не смотрел что это такое. Как-то не приходило в голову искать в STL списочные структуры, но наверно там что-то ужасное...

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

Здесь кстати realloc бы сработал если содержимое будет аллоцироваться в отдельной куче/пулле и вектор только хранит указатели. В этом случае решаются проблемы и внутренних и внешних ссылок. Если предствить что аллокатор максимально ткпой и использует только morecore/brk - смысл в realloc теряется

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

Если там разные потоки что-то постоянно переаллоцируют в одном регионе кучи - то и без std::vector с произврдительностью будут беды.

У вас стандартный аллокатор, стандартные треды и стандартный std::vector. Как вы собираетесь контролировать в одном регионе кучи треды что-то делают или в разных?

Ручек для управления нет.

Если вы меняете штатный аллокатор на какой-то готовый продвинутый (из числа упомянутых выше), то от realloc-а у вас вообще выхлопа не будет.

программа доожна стремиьься быстро аллоцировать всё что нужно

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

И он боольшую часть времени должен заниматься аллокацией объектов? Или всё-таки обработкой запросов?

А у вас обработка – это что-то, что не нуждается в выделении памяти от слова совсем?

Распарсить заголовки запроса в которых будут параметры для работы, распарсить тело запроса, создать ответное тело (например в JSON-е) – тут у вас будет куча аллокаций.

Как-то не приходило в голову искать в STL списочные структуры

А откуда список возьмется? Нужно написать вручную свой?

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

Если вы меняете штатный аллокатор на какой-то готовый продвинутый (из числа упомянутых выше), то от realloc-а у вас вообще выхлопа не будет.

Ни один аллокатор поголовно не умеет в realloc? Или это особенность какого-нибудь отдельно взятого mimalloc?

А у вас обработка – это что-то, что не нуждается в выделении памяти от слова совсем?

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

Распарсить заголовки запроса в которых будут параметры для работы, распарсить тело запроса, создать ответное тело (например в JSON-е) – тут у вас будет куча аллокаций.

Это конвертация данных а не обработка. Если она занимает боольшую часть времени, то это печально. Да, микросервисы нередко только этим и занимаются - преобразовать http запрос в sql, поспать пока БД просрётся, ответ упаковать в json и отправить по http. Только вот реальной обработкой тут будет заниматься БД, а не микросервис. И даже в этом случае боольшую часть времени должно занимать ожидание ответа БД и других сервисов, а не (де)сериаизация в json

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

А у вас обработка – это что-то, что не нуждается в выделении памяти от слова совсем?

Если очевидно, что на каждый запрос нужна память, почему не иметь её зарание? Пул контекстов, например? Обработал — положил назад. Что за задача такая, когда на запрос может НЕ потребоваться выделять память? А если память нужна всегда, то зачем дергать аллокатор?

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

Ни один аллокатор поголовно не умеет в realloc? Или это особенность какого-нибудь отдельно взятого mimalloc?

Вы специально не читаете то, что вам пишут или оно само собой так получается?

Продвинутые аллокаторы держат пулы блоков фиксированного размера. Например, блоков по 64 байта, по 128 байт и т.д. Блоки в этих пулах не объединяются. Т.е. если вы запросили блок для 80 байт, вам выдадут блок из пула для 128. И если вы вызовете realloc для этого блока чтобы увеличить его размер с 80 до 110, то там же вы и останетесь.

Но тут в дело вступает growth factor для вектора. Который от 1.5 до 2.0 (насколько я помню, 2.0 встречается чаще). И вот у вас в векторе 80 байт, вам нужно добавить еще один элемент, 80 умножается на 2.0 и получается 160. realloc не срабатывает, т.к. блоки в одном пуле не объединяются. Вам выдадут блок на 256 байт из другого пула. И это будет другой блок, куда содержимое надо будет скопировать.

А когда 160 закончатся и вы еще раз умножите на 2.0, получится 320 и вам выдадут новый блок размером в 512.

Так что не в одном realloc-е дело, но еще и в политике роста вектора при заполнении.

Зависит от того, какая это обработка.

Именно.

Под тяжёлые алгоритмы память обычно выделается заранее

Тут бы еще понять что значит «тяжелые».

Это конвертация данных а не обработка.

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

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

Если она занимает боольшую часть времени, то это печально.

Это вы уже что-то от себя выдумываете. Я не говорил, что аллокация занимает большую часть времени. Хотя в зависимости от задач вполне может быть так, что вся обработка будет требовать множество аллокаций/деаллокаций.

Так а что на счет «списка»? Его нужно вручную реализовывать?

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

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

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

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

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

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

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

да, в этом случае в момент роста списка будут лишние копирования. Здесь помогут независимые пулы памяти под каждый поток-воркер. Желательно с аллокатором, который умеет в растущие аллокации, благо современные ОС имеют механизмы для этого. Для json возможно будут строиться списки и хэшмапы, а не просто вектора, ведь там аллокация строк будет чередоваться с аллокацией узлов, да и структуры чаще имеют вид словаря, а не просто списка

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

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

Так а что на счет «списка»? Его нужно вручную реализовывать?

Я обычно так и делаю, достаточно прикрепть некоторую голову к элементу и написать класс-обёртку, которая по этим головам будет итерировтаь узлы.
И судя по всему не только я, например в llvm есть такое:
https://llvm.org/doxygen/classllvm_1_1simple__ilist.html

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

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

Нижнюю границу оценить можно? Медиану оценить можно? Или сколько обычно нужно памяти, чтобы оценить в рантайме, сколько нужно памяти? Скажем, 1КБ хватит для контекста и зоголовков, чтобы не дёргать аллокатор 1+C1*кол-во_заголовков+C) раз на каждый запрос, посчитать (прикинуть) сколько памяти надо дальше и тогда уже вызвать аллокатор? Если хватит — то это всего полгига на 500000 контекстов.

Можно использовать политику «под каждый запрос свой аллокатор» на базе арены или чего-то подобного.

Тоже отличная штука!

могут ли объекты, созданные в процессе выполнения запроса

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

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

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

Я это вижу так:
имеются фиксированные N потоков-обработчиков запросов. Каждый может иметь свой отдельный аллокатор. Каждый может так же преаллоцировать некоторое пространство, необходимое большинству запросов.
В этом случае не будет случаев false sharing, не будет конкурирующих аллокаций и не надо тратить время на запуск потока для запроса
Проблемы в случпе если объекты живут дольше обработки запроса это конечно не решает, в идеале такие объекты сразу размещать вне локальных пулов

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

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

Так как раз упомянутые выше продвинутые аллокаторы по этому принципу и работают. У них свои арены под каждый тред. И выделение/освобождение памяти внутри треда очень быстрое. Некоторые тормоза начинаются только когда объект из одного треда передается во владение другому треду.

Однако, все это уходит очень далеко от конкретного std::vector. Вы сказали свое «фи» по поводу использования вектора, хотя std::vector это даже и не вершина айсберга.

Так же в этих случаях избегают форматов вроде json

Это вообще может быть вне зоны возможностей разработчиков конкретного микросервиса. Нужен JSON для обмена с внешним миром – и все.

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

Это интрузивные списки. Которые лучше, чем std::list, но у которых все равно плохо с локальностью данных и нет поддержки произвольного доступа.

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

Слишком много вопросов для абстрактной трепотни на форуме.

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

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

но у которых все равно плохо с локальностью данных и нет поддержки произвольного доступа.

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

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

Но fork() всё равно является лютым недоразумением, и не только из-за overcommit.

А я давно предлагаю (и может быть сделаю в своем ядре) процессы без родителя. По факту родитель процессу нахер не нужен.

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

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

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

почти всё юзабельное написано на цепепе именно в девяностых-нулевых

Так это закономерно. После сишки плюсы были глотком свежего воздуха. Только ты уверен, что в этих юзабельных программах до сих пор C++98/03? Или таки уже переписали на 23?

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

Только ты уверен, что в этих юзабельных программах до сих пор C++98/03? Или таки уже переписали на 23?

Да вроде никто особо не переписывал ничего на 23 и даже на 17. В худшем случае что-то слегка правили чтобы новая версия компилятора варнингами и ошибками не плевалась. А вот чтобы намереннно запихивать в код новые фичи из новых стандартов - не замечал такого.

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

И когда же появился gcc-16.1?

У разрабов софта было достаточно времени чтобы написать что-то значимое с использованием gcc-16.1?

PS. Возможно, в GCC-16 еще не все гладко с поддержкой модулей из C++20.

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

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

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

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

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

Тоже с таким столкнулся. Радостно втащили std::format в embedded-проект. У меня прошивка сразу распухла на 160Кб. Бодался с ними, вроде бы успешно.

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

что в приведённом примере не так? патроны однажды закончатся. и сколько останется профессоров? на интервале лет 20?

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

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

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

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

Яростно плюсую. После C++11 Boost умер. После C++20 умерло ещё куча всего. В целом на современных проклятых крестах можно пейсать достаточно простой лаконичный код. Никогда не понимал людей, которые залезут в самый тёмный угол крестов, понапишут каких-то ссаных макросов, понавыделяют руками памяти, понапишут __attribute__ и прочей черни и потом ржут какой язык уротский. «Ты зачем это всё понаписал тут», - хочется спросить их.

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

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

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

Знаешь, почему веб-дрисня захватила мир? Потому что она дешёвая.

Ни системная дрисня, ни веб дрисня, если хорошо сделаны, не являются дешёвыми. Нынче в вебе даже бинарный код запускается и веб-приложения могут быть разными: одни написаны криворукими дураками, типа сайт Авито, где пока тыща JS скриптов не подгрузится даже кнопку сортировки объвяления нельзя нажать и проц думает о чём-то 10 секунд, а могут быть как веб-версия телеги, где всё достаточно молниеносно и даже анимированные стикеры вертятся достаточно шустро с использованием WASM и прочего там такого.

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

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

Ни разу не встречал реально хорошо знающего C++ и постоянно на нём пишущего, кому бы пришло в голову ходить рассказывать об этом. Или сдавать экзамены, доказывающие это. Чел просто тихо пишет на нём, достигает любых своих целей и в гробу вертел что об этом думают какие-то аноны в интернетике. Всё равно обосрут. Скажут надо было иначе пейсать, ты кривой дурачок. А у чела цель была сделать шоб работало и в профиляторе процессор не ело, а не язык знать - на язык ему вообще глубоко похрен, он пишет на чём попало, красивых языков для него не существует по определению, каждый имеет недостатки и придуман каким-то земным мясным человеком. Так чё пыжиться, лучше какой-нибудь один (на котором можно сделать ВСЁ) знать на каком-то достаточном уровне, чем постоянно пытаться найти идеальный) Ну и тут можно привести тот мем, что чем мудрее мудрец, тем в знании меньшего числа вещей он уверен. Так-то!

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

Клоун прав, 99% сишников си не знают. Да и вообще, зачем «знать» ЯП? Или вообще, любой язык, даже человеческий, зачем его знать? На нем надо говорить, передавать информацию, а не знать. И ЯП не надо знать, на нем надо программы писать. Что подавляющее большинство людей и делает.

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

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

А плюсы и есть на много замечательнее веб-дрисни. Просто средний человек на столько туп, что веб-дрисню он еще с горем пополам выучить может, а на плюсы мозгов уже не хватает. Это было лет 40-50 назад, когда программистом мог себя называть только математик с соотв. образованием, но потом работы стало на столько много, что берут всех. Вот и результат.

Сейчас это давно не так. Всё это время авторы браузеров и клепатели сайтов не дремали. Успехов разобраться во всех нюансах работы браузера, особенностях сотен видов HTMLElement с их событиями, особенностями работы CSS и рендера всякой невообразимой сракотани, тоннах свойств у любого простейшего события, всяких там обработчиках, разных встроенных JS API типа звука, webgl и прочего с вебсокетами и прочей фигнёй. Там уже даже антиотладочных приёмов развелось гора, чтобы твой сайт не дебажили - F12 нажал, консоль открыл - айпишник твой забанили, почистил куки, дропнул всё содержимое браузера, зашёл из анонимус-вкладки - всё равно по фингерпринтам узнали и забанили, бугага. Не-не, веб-дристунам платят уже больше C++ кнопкодавам!

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

Qt - это формы клепать под разные платформы. Потом они туда натащили кучу хлама от рендеринга HTML до звука и сетей. Получилось жирное. Не понимаю такого подхода, предпочитаю чтобы либа для клепания окошек занималась только окошками. Основная-то ценность в Qt была в клепании окошек кросс-платформенно, зачем они себя изговняли попыткой делать кросс-платформенно и всё остальное, что плохо лежит? Стрёмный подход достаточно. Психологически Qt воспринимается как жиробасина, которая физически не может быстро работать, я даже удивлён что его в Embedded видно. Казалось бы, там надо каждый такт экономить. Это тот Embedded где ты в экран тыкнул и оно 200 миллисекунд думало вместо того, чтобы сразу отреагировать? Ясно. Мне скажут, ты дурачок ссаный ещё бы религию приплёл, какая разница что там у тебя за психологические ощущения, ты бы скомпилировал да профайлером посмотрел, всё там зашибись! Да, понимаю, может там всё и так. Но чё-то недавно нужно было окошек поклепать в десктопе, взял FLTK - кайфанул с минимализма. Да, кривое оно достаточно в сравнении Qt наверное, мануалы вообще алкаши сочиняли, какие-то там версии непонятной совместимости - фиг поймёшь чё устанавливать. Но в целом минимализм победил.

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

У нас там было какое-то управление подъемными кранами. Без UI, без сети, без ничего зачем надо было бы тащить полгиговую либу. Нормальный человек бы на Си это реализовал, но там наняли заедушника, который ничего больше по жизни не осилил и таскает за собой свой самилучши инструмент везде куда возьмут. И солько еще таких по миру.. Кстати, автомобильный эмбед – тот еще пример помойки, доверху набитой анскильными ламерами.

Хотелось бы отметить то же наблюдение: интерфейсы повозок просто ад по скорости реакции. Даже на древнем ипхоне корпорация аппле осилила добиться «прям сразу» реакции на нажатия пальца по экрану, а когда тыкаешь в экран на мерседесовских интерфейсах, то оно обсчитывает лекарство от спида. Хотя в автомобиле можно позволить себе потратить больше энергии на анимации и вычисления, чем в ипхоне. Особый фасепалме я ощутил видя на видосах автообзорщиков новые роллс-ройсы или бентли, в которых ставили ущербные кривые TFT-экраны с косой цветопередачей и с ещё более тормозным отсталым интерфейсом.

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

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

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

MYLOG("time is {:T}, digit {:016x}, hello {}, world {}", time(nullptr), 0x12345, 1, 2);

А большего мне и не надо. Тупейшая задача - «перестать писать %d» была решена.

tcpfinhello
()
Последнее исправление: tcpfinhello (всего исправлений: 4)
Ответ на: комментарий от eao197

Новые версии стандарта дают фичи, попробовав которые назад возвращаться уже не хочется.

Например, для C++14 это простой auto для вывода типа функции, decltype(auto), полиморфные лямбды и выражения в capture list для лямбд.

В C++17 это [[nodiscard]], fold expressions, if constexpr, structured binding, inline variables.

В C++20 это concepts, operator<=>, шаблонные параметры для лямбд, consteval/constinit, [[no_unique_address]] (к сожалению, с его поддержкой пока есть проблемы), короутины.

Кто-то даже кипятком писает от ranges в C++20.

В C++23 это deducing this и if consteval.

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

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