LINUX.ORG.RU

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

 , , ,


3

4

Мой предыдущий тред в 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)
Ответ на: комментарий от shdown

а вы на каком языке программируете в Zed? проверьте папку .local после установки обычно там появляется zed, проверьте инфраструктуру языка например С/С++ помойму это clangd, соотв. в файл settings.json, надо добавить упоминание своего лсп, далее, надо создать .clangd если это clangd и на сайте clangd посмотреть пару строк, которые туда надо вставить и всё начнёт подсвечиваться. вот файл .clangd он должен быть в корне проекта, у Cmake, можно генерировать compile_commands.json про это так же на сайте clangd написано если интересны возможности clangd

CompileFlags:
  Add:
    [
      -std=c++26,
      -stdlib=libc++,
      -I/usr/lib/llvm-20/share/libc++/v1,
      -fmodule-file=std=std.pcm,
    ]

Как написать бинарный поиск на китайском?

?

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

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

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

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

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

?

Два с половиной человека в этом треде думают, что вы нейросеть, которой дали пароль от ЛОРа. Если нет (или даже да), напишите мне на почту в профиле, будем ставить на вас опыты.

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

соотв когда cond1 cond2 cond3 cond4 и такой пример, это пример оторван от реальности

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

тут написать можно как угодно

Ну так напишите «как угодно» (оригинальную функцию jjhash_s) по-другому, померяем производительность.

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

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

как думается и должен прелестно хавать — ибо:

R operator()( Args... args ) const;


состояние меняться может у самого «захваченного» объекта, посколку конструктор `std::function` «may throw std::bad_alloc or any exception thrown by the copy constructor of the stored callable object.»

safocl ★★
()

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

Можно поконкретнее — какое «безумие» то?
На счёт «не пользуется» — вот уж враки, очень распространены уже давно — многие передовые либы (АПИ) предоставляют так же сразу и модуль.

safocl ★★
()

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

неплохо бы формализовать «серьёзность производительности», а так же объявить критериальностности отнесения к таковому.

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

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

потому что код с std::simd в два-три раза тормознее чем со сторонними библиотеками, просто голыми интринсиками, и даже медленнее чем просто цикл for.

а ты не путаешь язык программирования с конкретной реализацией компилятора?

существуют в Boost и сделаны там гораздо лучше.

снова же — хорошо бы формализовать «лучшевость», а так же объявить критериальностности отнесения к таковому.

Абсолютно тормозные контейнеры map, set, unordered_map

симметрично с предыдущим про «тормознутость»

Rust (BTreeMap) и другие реализации B-Tree Map их обгоняют по производительности

а у них точно такие же гарантии, как в перечисленных из стандартных в С++?

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

а ты не путаешь язык программирования с конкретной реализацией компилятора?

Я другой анон, но вот тоже слышал много нехорошего про std::simd

И мне представляется что если есть тема поднять производительность за счет правильного использования simd то оптимально взять что-нибудь специализированное типа intel ispc и скомпилировать отдельным объектным файлом.

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

поднять производительность за счет правильного использования simd то оптимально взять что-нибудь специализированное типа intel ispc и скомпилировать отдельным объектным файлом.

а откуда инфа, что как раз это нельзя сделать для std::simd?

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

сначала надо откатить назад все goto, далее надо убрать if(с…=… привести это в стейт точное переходное состояние, возврат надо static_cast<uint32_t>(a…) наверно, и (uint8_t) => static_cast<uint8_t>, имея длину и офсет по строке вы идете по строке и так далее

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

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

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

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

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

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

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

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

    uint32_t off=0;
    uint32_t start=0;
    while(off<n){
        // start=off;
        
        if(s[start+off]=='\0'){start=0;std::cout<<"ddd1"<<std::endl;break;}
        if(s[start+off+1]=='\0'){start=1;std::cout<<"ddd2"<<std::endl;break;}
        if(s[start+off+2]=='\0'){start=2;std::cout<<"ddd3"<<std::endl;break;}
        if(s[start+off+3]=='\0'){start=3;std::cout<<"ddd4"<<std::endl;;break;}


        off+=4;
    }
    // off=start;
    return off+start;
anonymous
()
Ответ на: комментарий от anonymous

первая задача любой задачи связаной со строкой вообще самая первая передать строку и организовать удобный проход если это не std::string, у нас const char* мы должно организовать в первую очередь для себя удобный проход по строке!

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

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

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

Кстати, сам дурак, в смысле, бряки забыл после каждого act4(). Поигрался я тут - https://godbolt.org/z/cneG5xEGe - loop_run с точностью до минимальных переобозначений равен гоутушной версии, это если цель была по максимуму развернуть циклы ( заплатив за это большими мутностью и размером). Но я так слышал, и давно уже, что разворот циклов не имеет сейчас такого оптимизационного смысла, как повезёт.

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

нет, как правило goto это отрицание управляющего состояния, когда мы пишем в лоб разворот цикла, мы сейвим готу, а вообще задачу надо развернуть обратно, граничные состояния строки 0 - н, нету там бесконечности, можно идти до ‘\0’, а может можно проверить есть ли он там, тогда если мы проверили есть ли он там и грузим по четыре*(4 это не 5 и целое число, там остаётся хвостик который управляемый), то управляющая последовательность идёт отдельно от управляемой, вот и получается, что вроде чото прячем, но на деле с нюансами(какими не буду даже намекать).

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

тогда переписывая ваш пример это будет,


нужна длина строки
узнали точный ровный конец по 4, пометили хвостик 0 1 2 3
пробежались по 4 состояния сохранились

отдельно прошлись по хвостику.
anonymous
()
Ответ на: комментарий от zurg

Так а вы что предлагаете? Дублировать код? Или вызывать функции по указателям?

С дублированием кода у меня получилось замедление для маленьких N (<= 5) на 10-15%. Это ещё хорошо, что компилятор понял, что всё это добро нужно за цикл вынести.

https://gist.githubusercontent.com/shdown/3208917403b401bbc9939dddd2b4de4d/ra...

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

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

проблема состоит в выборе управления, если мы пологаемся на готу то ваш пример и пример, который вы назвали повторением кода валиден, если мы хотим управлять не через готу, тогда придётся ужиться с граничащими ситуациями когда хвост в начале тоесть -1 блоков, когда хвост следующий и когда хвост равен концу блока, так как вы выбрали управление через кондишон if(!…) goto это обьяснить будет сложно вам, и там далеко дело не в копировании кода.

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

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

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

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

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

у меня получилось поймать все случаи с управлением состояний явно,


    uint32_t off=0;
    uint32_t start=0;
    //расчет блоков, расчет хвоста, расчет индекса хвоста, расчет индекса начала хвоста
    while(off<n){
        // start=off;
        
        if(s[start+off]=='\0'){start=0;std::cout<<"ddd1"<<std::endl;break;}
        if(s[start+off+1]=='\0'){start=1;std::cout<<"ddd2"<<std::endl;break;}
        if(s[start+off+2]=='\0'){start=2;std::cout<<"ddd3"<<std::endl;break;}
        if(s[start+off+3]=='\0'){start=3;std::cout<<"ddd4"<<std::endl;;break;}

        //это частный случай
        off+=4;
    }
    switch()
    {
      case:3{}
      case:2{}
      case:1{}
      case:0{}
    }
    // off=start;
    return ваш возврат;//это только идеальный случай когда блоков по 4 больше -1 и 0 тоесть по4=1 и 1 хвост до 3 байта.

соотв есть несколько случаев типо.

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

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

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

соотв. то чего вы боялись спрячет весь код на кланге такой компиляцией, но я не смотрел на локалке я выхлоп смотрел в годболте clang++20+ -std=c++23 -msse4.2 -O3 -funroll-loops

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

соотв. один из вариантов, как только вы уберете готу, сделаете управление от хвоста и будете ловить эти-3-4 состояния хвоста, то в начале у вас будет 1-3 операции, потомучто когда на входе «111\0» это значит у вас нету даже цикла, соотв если убрать макросы, и сделать всё локально там вроде что-то красивое получается наверно.

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

Какой ещё n, дурак — это ж нуль-терминированная строка, мы длину заранее не знаем.

«Ничего не поделаешь, придётся всё делать заново» ©

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

Можно, но в чем смысл? Вся суть std::simd в том чтобы не вылезать за пределы уютненьких плюсов и умный компилятор сам собой сделает все по красоте. Если не вылезать то никакой пользы от отдельной компиляции нет

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

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

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

ситуация когда принимаем const char* string, даже если с н, придётся проверить н проверить точный размер строки, конец строки, свести всё это в проверки, и своим лейаутом мы ограничиваем максимум, вариант с string посейфовее, просто проверяем, проверяем если есть еще какие-то ситуации, и идём как раньше точно также по выделенному, наверно можно пойти дальше, сначала выделить источник в свой источник, потом выделить возвращаемый лейаут, и идти уже по выделенному источнику, а так да, фигня не знаю как \0 гарантировать и поймать все проверки на сайзе.

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

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

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

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

И как вы это проверите?

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

Так что ваша проверка вовсе не гарантирует, что именно после последнего символа строки имеется нулевой символ.

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

Так что ваша проверка вовсе не гарантирует, что именно после последнего символа строки имеется нулевой символ.

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

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

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

если строка не имеет на конце завершающего символа, а через 2 байта \0 поидее в цикле где явно смотрим \0 это выявится.

я пока сам не выкупил точного прикола, но char bad_array[6] = {’ ’, ’ ’, ’ ’, ’ ’, ’ ’, ’ ‘}; char bad_array[7] = {’ ’, ’ ’, ’ ’, ’ ’, ’ ‘, ’ ‘,’\0’};

это не тоже самое что

const char* s=" \0";

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

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

придется в начале делать проверки наверно

соотв случаи, передаем только указатель его проверяем, бежим по строке до \0 фиксируем размер, и циклом обработки всё равно бежим до \0 наверно но уже с алгоритмом, по известному размеру чото такое.

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

походу наверно вы правы, кароче да интересно

Посмотрел приведенный пример.
Для строк типа char s6[4]={’ ‘,’\0’,’ ‘,’ ’}; нужно использовать не строки с нулевым
символом в конце, а строки а-ля CString.

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

Для строк типа char s6[4]={’ ‘,’\0’,’ ‘,’ ’}; нужно использовать не строки с нулевым

символом в конце, а строки а-ля CString.

А ещё много лучше управлять памятью не надеяь на какой-то там API, который использует malloc(), а
своим API, которое отвечает за выделение и освобождение памяти.
Просто в основном веду разработку системного API которое не полагается на чужое API.

Вкусный борщ не приготовишь без капусты и хорошее API не разработаешь полагаясь на std, STL, ООП, стандарты C++, …
Поверьте на слово.

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

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

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

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

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

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

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

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

на системе где разрешено создание char[] и «ой я забыл \0» очевидно надо всё свести, к задаче что сначала курица или яйцо, очевидно создать нельзя char[] нельзя создать char[] надо требовать \0, потребовали \0, можно начать думать как быть с размерами, а пока char[] существует и в курице и яйце это бесмыссленно…

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

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

char[4]={' ',' ',' ',' '};

как узнать что его размер 4 а не 10 например.

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

а системе где разрешено создание char[] и «ой я забыл \0» очевидно надо всё свести, к задаче что сначала курица или яйцо, очевидно создать нельзя char[] нельзя создать char[] надо требовать \0, потребовали \0, можно начать думать как быть с размерами, а пока char[] существует и в курице и яйце это бесмыссленно…

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

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

вы же не сможете узнать размер

Если это не задача реверсеинжиринга, то такого рода проблема может возникнуть лишь при плохом API или архитектуре проекта.

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

да нет не реверсинженеринга, это прямая задача

она звучит просто

char[4]={' ',' ',' ',' '};

нужно знать её размер, не доверяя вводу, тоесть если она 4 она должна быть 4.

anonymous
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.