Ответы пользователя по тегу C++
  • Диапазон типов данных C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Хочу порекомендовать тебе материал от разработчиков PVS-Studio относительно моделей данных в C++. Там хорошо и коротко объясняется суть твоего вопроса.
    Так же тебе будет полезно изучить документацию C++, где можно изучить подробности.

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

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

    Поэтому, в общем смысле на твой вопрос ответить можно так. Если клиентская система ожидает от процесса одну модель данных, а он работает в другой, то падения процесса не миновать. В ряде случаев процесс даже не загрузится в память, т.к. загрузчик бинарного кода может по косвенным признакам определить несовместимость процесса и системы у клиента.
    Ответ написан
  • Чем отличается mvpmatrix от тех которые заданы по отдельности и надо ли их подключать (по отдельности) и в каких случаях?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для лучшего понимания материала тебе стоит изучить, как минимум, эту статью.
    Если коротко
    ModelMatrix - это матрица преобразования локального пространства геометрии конкретного объекта.
    Внутри этого локального пространства геометрия объекта расположена относительно центра пространства. Чтобы геометрию модели отобразить в каком-либо целевом пространстве, необходимо произвести отображение локального пространства модели в целевое пространство. Именно таким отображением является ModelMatrix.

    ViewMatrix - матрица отображения любого внешнего пространства в пространство вида - пространство взора наблюдателя. Наблюдатель (камера) воспринимает объекты только внутри своего собственного локального пространства. Чтобы наблюдатель мог увидеть объект, объект необходимо отобразить из его родного пространства в пространство вида камеры, где он или попадет в область проекции, или не попадет.

    Projectionmatrix - матрица отображения видового пространства в пространство проекции вида. Фактически это отображение выполняет проецирование некоторого участка видового пространства на плоскость. Именно отображенная таким образом геометрия становится видимой при презентации кадра.

    Каждый объект сцены находится и анимируется внутри своего локального пространства. Чтобы объект на сцене встал в положенное ему место, его геометрия отображается из своего локального пространства в глобальное пространство сцены с помощью своей ModelMatrix.
    Чтобы сцена стала видна для камеры, всю ее геометрию требуется отобразить в пространство вида камеры с помощью матрицы ViewMatrix.
    Чтобы часть пространства вида камеры попала в область презентации кадра, всю геометрию видового пространства нужно отобразить в пространство проекции с помощью матрицы Projectionmatrix.
    При этом важно заметить, что матрицы Projectionmatrix и ViewMatrix уникальны для камеры, а вот матрицы ModelMatrix уникальны уже для каждого объекта отдельно.


    Отображение между пространствами выполняется через операцию перемножения позиции в исходном пространстве на матрицу преобразования пространства. Если подходить к этой задаче в лоб, то отображение каждой позиции потребует 16 операций перемножения над вещественными числами.
    Всего для заполнения области презентации кадра требуется выполнить, как минимум, три последовательных отображения геометрии, что будет означать по 48 операций умножения на каждую позицию отображаемой геометрии.

    Учитывая что Projectionmatrix и ViewMatrix в процессе презентации кадра являются неизменными, затраты в 48 операций на каждую позицию выглядят как расточительство. Если до презентации выполнить отображение самого пространства вида в пространство проекции, т.е. перемножить ViewMatrix и Projectionmatrix в правильном порядке, то с помощью результирующей матрицы viewProjectionMatrix число операций на одну позицию можно снизить до 32.
    Если же произвести отображение самого локального пространства модели в пространство презентации через перемножение матриц viewProjectionMatrix и ModelMatrix в правильном порядке, то благодаря полученной таким образом MVPMatrix число операций на одну позицию снизится до изначальных 16.

    Таким образом, матрицы viewProjectionMatrix и MVPMatrix просто позволяют снизить трудоемкость презентации кадра. Однако, во время презентации может потребоваться определение положения геометрии в каком-либо промежуточном пространстве, поэтому вдобавок к MVPMatrix в шейдер принято отдавать и ModelMatrix, и матрицы камеры.
    У каждой из матриц свой смысл. И если MVPMatrix бесспорно нужна всегда, то любую другую матрицу в шейдер добавлять стоит только исходя из осмысленной необходимости. Регистры GPU для шейдера не резиновые и могут очень быстро забиться избыточными данными. К тому же, чем меньше на каждом кадре в шейдер передается данных, тем быстрее выполняется презентация кадра.
    Ответ написан
  • Можно ли использовать sfml без openGL в C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что OpenGL является т.н. GAPI, т.е. интерфейсом прикладного программирования графики.
    На базе такого GAPI строятся движки, фреймворки и простые проекты. SFML, будучи фреймворком, точно так же базируется на OpenGL для работы с графикой.

    В связи с этим, если ты не планируешь работать с графикой, то можешь просто не использовать модули Window и Graphics. Как следствие, у тебя не будет возникать зависимости от OpenGL.
    Но если для тебя важно пользоваться графикой SFML, то в его стандартной поставке без OpenGL твой проект не соберется.
    Ответ написан
  • Возможно ли перенести "внешний" интерфейс одной библиотеки в другую через статическую связку?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Судя по описанию вопроса, проблема в том, что между кодом основного модуля сборки и кодом статической библиотеки, от которой зависит основной код, нет прямой функциональной связи.

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

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

    Есть несколько способов обойти правило компоновки библиотек.
    Первый и самый простой: вынудить компоновщик обработать всю библиотеку целиком. Это делается через представление библиотеки как Whole Archive[?].
    Однако, в этом случае вся статическая библиотека будет скомпонована в исполняемый код. Это не всегда бывает удобно. Особенно если в библиотеке находится много отладочного кода.

    Второй вариант - это использовать __attribute__((used))[?] или [[gnu::used]] на новый лад.
    В коде для Visual C++ можно использовать #pragma comment(linker: "/include:")[?].
    Данные конструкции помечают функцию как важную для компоновки, в результате чего функция всегда будет компоноваться из статической библиотеки в бинарный код.

    Вариант третий - это использовать опцию -u[?] компоновщика.
    Для Visual C++ такой опцией будет /INCLUDE[?].
    Указание важных функций для принудительной компоновки прямо в сценарии сборки является наиболее прозрачным и удобным методом с точки зрения дальнейшей поддержки кода.
    Ответ написан
  • Ошибка при создании явной специализации. В чём ошибка?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    template <typename T>
    T maxn(T arr[], int arrSize);

    Это - общая форма шаблона функции. Если тебе нужно сделать явную специализацию шаблона, твоя явная специализация должна соответствовать этому общему шаблону.

    template <>
    const char* maxn(const char* arr[], int arrSize);

    Сырые однобайтовые строки в C++ (при соблюдении правила нуль-терминации) определяются типом char* или const char* если строку не планируется модифицировать. Наша функция не планирует модифицировать строки, следовательно явная специализация шаблона должна работать с типом const char*.
    Тип const char* должен быть подставлен везде вместо параметра шаблона T.

    Таким образом явная специализация шаблона для сырых строк будет полностью соответствовать общему шаблону функции.

    А теперь немного о твоей ошибке.
    template <> char* maxn(const char *arr[], int arrSize);

    Это объявление не является явной специализацией приведенного выше шаблона. Это - определение перегрузки функции через явную специализацию какого-то иного неописанного шаблона. Это потому что результат функции char* отличен от параметра функции const char*.
    В целом, эта запись является плохо сформулированной (ill-formed) по целому ряду причин, включая и то, что перегрузка не может осуществляться через изменение типа результата функции.

    В целях обучения я советую не опускать перечисление аргументов явной специализации шаблона:
    template <>
    const char* maxn<const char*>(const char* arr[], int arrSize);

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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Это простой способ поделить строку на подстроки.
    inline std::vector<std::string_view> SplitString( std::string_view input_string, const char separator )
    {
    	std::vector<std::string_view> parts;
    
    	size_t part_length = 0;
    	while( ( part_length = input_string.find( separator ) ) != input_string.npos )
    	{
    		parts.emplace_back( input_string.data(), part_length );
    		input_string.remove_prefix( part_length + 1 );
    	}
    
    	if( !input_string.empty() )
    	{
    		parts.push_back( std::move( input_string ) );
    	}
    
    	return parts;
    }
    Ответ написан
  • Какие языки совместимы с C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Судя по комментариям к ответам, у тебя формулировка вопроса не соответствует твоим ожиданиям.
    Совместимость языков - это тонкий маршалинг типов и совместная линковка в один бинарный файл.
    Этим условиям, в разной степени, удовлетворяют: C, asm, D, C#, Java. Может быть еще какие-то языки. При этом, у каждого языка верхнего уровня будут свои требования к маршалингу типов и передаче управления.

    Тебя же интересуют встраиваемые языки.

    Взять, например, D. Он полностью совместим с C++. Однако, его требования не позволяют простую линковку объектных файлов C++ и D в один бинарный файл. На C++ или должен быть создан D Runtime (что не так просто сделать), или C++ код должен линковаться в бинарник на D, в качестве Better C++. Т.е. получается наоборот, в базе будет D, а C++ его только расширяет.
    С питоном и Java все точно так же. C# вообще работает с библиотеками классов, написанными на C++/Cx, т.е. на модифицированном C++.
    Эти языки не являются встраиваемыми. Их возможности расширяются за счет использования C++.

    Хорошим ответом на твой вопрос будет вот такой список встраиваемых языков. Из этого списка можно брать любой язык, ядро которого написано на C или C++.
    Мой личный выбор - это AngelScript и Lua/Terra.
    Ответ написан
  • Android NDK Error: no matching member function for call to 'push_back'?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    class rck{
    public:
        unsigned short x,y;
        void show();
        int tm=0;
        uint8_t type=0;
    };

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

    Лучше будет с молоду учиться правильно оформлять свой код. Например так.
    struct rck final
    {
    	uint16_t x = 0;
    	uint16_t y = 0;
    	int32_t tm = 0;
    	uint8_t type = 0;
    	
    	
    	void show();
    };

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


    У тебя объявлен, вроде бы, агрегатный тип с неявным конструктором по умолчанию, в котором выполняется инициализация только полей tm и type.

    rock.push_back({(uint16_t)(x*64),(uint16_t)(y*64),0,0});

    Тут перед обращением к push_back формируется фиктивный std::initializer_list<uint16_t> с двумя элементами внутри.
    В общем смысле, до полного списка аргументов нужно указать еще два аргумента. Тогда эту запись можно будет считать агрегатной инициализацией временного объекта типа rck.

    Такая форма агрегатной инициализации стандартизирована в C++11. До C++14 агрегатная инициализация отключается для типов, в которых присутствует инициализация полей по месту объявления (в коде это поля tm и type). Каждый следующий стандарт требования к агрегатной инициализации только ужесточает.

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

    Все это должно дать понять что данный код не соответствует стандарту языка и не должен компилироваться. Подходящего конструктора от двух аргументов для типа rck не определено и тип не является агрегатным.
    То, что g++ смог собрать этот код, останется на совести g++.
    Еще одним важным моментом будет точное понимание, какой именно стандарт языка выбран для трансляции твоего кода.
    Ответ написан
  • Что лучше использовать #include или LoadLibrary?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вопрос, на самом деле, заключается не в выборе межу LoadLibrary и #include, а в выборе между явным и неявным связыванием.

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

    Явное связывание выполняется полностью кодом процесса, для чего и применяется набор функций для работы с библиотеками. Явное связывание - это большая рутина. Но иногда без этой рутины никак.
    Поиск динамических библиотек при работе LoadLibrary[?] осуществляется в соответствии с определенным порядком. При этом, путями для поиска динамических библиотек можно управлять в ручном режиме, добавляя новые или замещая имеющиеся пути поиска.

    Директива препроцессора #include[?] лишь косвенно относится к вопросу. На самом деле к вопросу относится директива #pragma comment( lib )[?].
    Прагма работает только для cl - компилятора от Microsoft. Подключаемая таким образом статическая библиотека должна находиться в перечисленных Library Path, в настройках сборки проекта. Эта прагма является альтернативой прямого указания статической библиотеки в настройках сборки проекта.
    Эту директиву любят использовать в сторонних заголовках, код для которых поставляется только в бинарном виде, в статических или динамических библиотеках. Вместе с динамической библиотекой такого решения поставляется и библиотека статическая, в которой находятся инструкции для неявного связывания с динамической библиотекой. Результатом работы #pragma comment( lib ) с такой статической библиотекой будет неявное связывание твоего кода с указанной динамической библиотекой.

    Размер PE ощутимо растет при компоновке из статических библиотек.
    Явное и неявное связывание слабо влияют на размер PE, но явное связывание усложняет жизнь разработчика, т.к. поддерживать загрузку интерфейса библиотеки разработчику придется самому.
    Ответ написан
  • Как передать в лямбду контекст класса?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Drottarutarnum, p = { s }; - это Copy Initialization.
    В буквальном смысле, что тут происходит. Сперва ты создаешь temporary local через конструктор Protocol::Protocol(Serial &s). Следом, через operator =, который сводится к конструктору копирования/перемещения, ты делаешь Copy Construction объекта p. Делается это через конструктор копирования/перемещения по умолчанию, который просто копирует/перемещает поля используя их конструкторы копирования/перемещения.

    Лямбда создается внутри temporary local значения, т.к. именно для него вызывается соответствующий конструктор. Далее эта лямбда остается жить в объекте Serial, а сам temporary local уничтожается сразу же после конструирования p.

    После этого в замыкании лямбды остается dangling pointer на уже уничтоженный this.

    Одним из возможных решений данной проблемы может быть не p = { s };, а p{ s }; или, как ты уже описал, p = s;. Такой тип инициализации уже называется Value Initialization. Но по своей сути это только отсрочка проблемы. Тебе придется очень пристально следить за жизнью такого объекта, т.к. вероятность обрести все тот же висячий указатель в замыкании в следствии неявного копирования крайне высока.

    Другим из возможных решений такой проблемы может быть использование std::enable_shared_from_this[?], std::shared_ptr[?] для хранения протокола и std::weak_ptr[?] в замыкании твоей лямбды. Этот метод гарантированно решит твою проблему, но может привести к ряду других проблем с возможной утечкой ресурсов.

    В качестве еще одного способа - научить Serial отцеплять лямбду уничтожаемого Protocol и написать правильный конструктор копирования Protocol, соблюдая правило 3/5/0.

    В общем смысле, тебе стоит внимательнее смотреть за тем, какие операции ты выполняешь над объектами, сырой указатель на которые ты хочешь где-то сохранить.
    Ответ написан
  • Как получать значения из потоков без использования future?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Если почитать твой код внимательно, то становится видно немало проблем.

    [&p, &all_files_and_sums]
    Если с захватом all_files_and_sums по ссылке я не спорю, то точно ли p продолжит существовать после выхода из итерации? Давай подумаем. А после выхода из цикла продолжит?
    У меня вообще нет уверенности в том, что после смещения bfs::directory_iterator адрес возвращаемого им bfs::directory_entry изменится. Я бы предпочел захватывать копию bfs::directory_entry в лямбде.

    У тебя нет ожидания окончания работы от пула потоков - нет синхронизации с завершением запланированных задач. Точнее... ну как нет... У тебя жесткая синхронизация через std::future::get[?]. По факту в этот момент у тебя блокируется главный поток до момента обработки запланированной задачи. У тебя всегда планируется только одна задача. А видимость конкурентной работы создается лишь потому что какой поток ее схватил, тот и работает. Видимо задачи у тебя быстро обрабатываются, раз ты глазами этого не увидел.

    Тебе стоит сохранять сами std::future от задач. В момент планирования их результат еще не определен и get вызывать не надо. Надо дождаться завершения работы всех потоков в пуле и исчерпания всех задач. Для этого у тебя в пуле должны быть продуманы механизмы оповещения.
    После обработки всех задач ты можешь вызывать std::future::get, получать результаты и производить свои операции над ними.
    Альтернативно, ты можешь более тонко реагировать на завершение каждой задачи и появление в ее std::future результата. Это тоже можно сделать. Просто сделать это надо своими руками и продумав масштабируемость такого механизма.

    И в дополнение. Зачем тебе boost? Ты пользуешься std::future и лямбдами, ты пишешь в рамках стандарта C++11. Тебе доступны и std::thread, и все примитивы барьерирования из std.
    В твоем распоряжении вся Thread Support Library. А boost тут явно лишний.
    Если переключишься на C++17, то тебе и boost::filesystem не будет нужна, т.к. станет доступна std::filesystem - Filesystem Library.
    Ответ написан
  • Как правильно передать в RegSetValueEx в lpData NULL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Начнем с простого.
    sizeof( expresion ) позволяет узнать минимально допустимый размер памяти, требуемый для того чтобы память могла вместить объект с типом результата утверждения expresion.
    sizeof( value ) вернет размер LPCWSTR, т.е. размер типа const wchar_t*.

    wcslen не допускает передачу нуля в качестве своего параметра. Забота об этом лежит полностью на тебе.

    RegSetValueExW может принимать нуль в параметре lpData, но тогда cbData обязан содержать 0. Но в целом, это очень плохая практика. Если тебе нужно записать пустую строку, пиши пустую. строку. Т.е. "", но не нуль.

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

    Далее, по самому коду. Его тяжело читать.
    Как читать будет легче
    bool modifyKey( HKEY root_hey, const std::wstring_view& path, const std::wstring_view& key_name, const std::wstring_view& new_value )
    {
       HKEY folder_key;
    
       if( RegCreateKeyW( root_hey, path.data(), &folder_key ) != ERROR_SUCCESS )
       {
          return false;
       }
    
       const BYTE* const value_buffer = reinterpret_cast<const BYTE*>( new_value.data() );
       const DWORD buffer_size = static_cast<DWORD>( new_value.length() * sizeof( wchar_t ) + 1 );
       if( RegSetValueExW( folder_key, key_name.data(), 0, REG_SZ, value_buffer, buffer_size ) != ERROR_SUCCESS )
       {
          RegCloseKey( folder_key );
          return false;
       }
    
       if( RegFlushKey( folder_key ) != ERROR_SUCCESS )
       {
          RegCloseKey( folder_key );
          return false;
       }
    
       RegCloseKey( folder_key );
       return true;
    }
    Ответ написан
  • Как создать папку с учетом имени пользователя c++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для современных версий ОС Windows в наборе WinAPI присутствует функция SHGetKnownFolderPath[?].
    Она позволяет получить путь к некоторым предварительно определенным папкам, набор которых описан абстракцией KNOWNFOLDERID[?].

    Тебе подойдет FOLDERID_Desktop для получения папки рабочего стола пользователя.
    При работе с этой функцией очень важно не забывать пользоваться функцией CoTaskMemFree[?] для освобождения памяти, переданной из функции через указатель ppszPath.

    Небольшой пример использования
    wchar_t* path_buffer = nullptr;
    if( FAILED( ::SHGetKnownFolderPath( FOLDERID_Desktop, KF_FLAG_DEFAULT, 0, &path_buffer ) ) )
    {
    	::CoTaskMemFree( path_buffer );
    	LOG_ERROR( LOG_CHANNEL, "Failed to get known directory; error - #{:08X}.", ::GetLastError() );
    	// terminate ...
    }
    
    std::wstring user_desktop_path{ path_buffer };
    ::CoTaskMemFree( path_buffer );
    
    // whatever with `user_desktop_path` ...
    Ответ написан
  • Android как создать папку средствами NDK?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Через NDK доступна функция mkdir[?].
    Ее поведение для Android ни чем не отличается от изложенного в официальной документации.

    Для проверки существования папки можно использовать функцию stat[?], а можно опереться на код ошибки EEXIST из mkdir.
    Ответ написан
  • Как правильно передать в метод массив и получить обратно массив другого размера?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе не нужны new и сырые указатели. Тебе нужен линейный контейнер с хранением элементов в непрерывном блоке памяти. Это будет или std::vector, или std::array.

    Для начала можно остановиться на векторе. Еще одним важным типом будет std::span[?] или gsl::span[?][S] если ты не можешь пользоваться C++20.

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

    Вот объвление твоей функции: gsl::span<float> Метод( gsl::span<float> values ).
    span - это не владеющий памятью тип, обозначающий участок непрерывной памяти с данными определенного типа. span очень легок и является value type - т.е. создан чтобы его передача по значению не приводила к ощутимым нагрузкам. span конструируется из std::vector, std::array, плоских массивов, std::unique_ptr<[]> и сырых блоков памяти.

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

    Псевдокод
    std::span<float> Метод( std::span<float> values )
    {
    	size_t processed_count = 0;
    	for( float& element : values )
    	{
    		// ... обработка значений
    		// ... изменение processed_count
    		// ... условия обрыва цикла
    	}
    	
    	return values.subspan( 0, processed_count );
    }
    
    int main()
    {
    	std::vector<float> values{ 7.83f, 14.1f, 20.3f };
    	std::span<float> processed_values = Метод( values );
    	
    	for( const float& value : processed_values )
    	{
    		std::cout << value << ' ';
    	}
    	
    	return 0;
    }
    Ответ написан
  • Зависит ли производительность от движка?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Из вопроса не удается понять критерии производительности, которые тебя интересуют. Вообще, тема производительности и профилирования настолько обширна, что о ней почти невозможно говорить в общем контексте. Производительность графического вывода кардинально отличается от производительности математической модели, производительности механик или производительности сетевого соединения. И все это будет кардинально отличаться от производительности подсистемы ресурсов и производительности системного пояса.

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

    По своему личному опыту еще могу сказать так. Если начинать разработку, то начинать лучше с языка как можно более высокого уровня. Ты выбираешь между C# и C++. Бери C# и начинай с него.
    И только когда твой код начнет упираться в потолок производительности, переноси его части на C++. Высокая производительность C++ компенсируется низкой скоростью разработки, большими требованиями к опыту разработчика, высокой ценой ошибки и малым градиентом вероятности ошибки по мере обретения опыта.
    Дополнительно, далеко не весь код должен быть на одном языке. Что-то динамично меняющееся резонно написать на легко обновляемом скрипте. C++ использовать стоит только там, где требования производительности перекрывают требования скорости разработки и простоты модификации.
    Ответ написан
  • Как написать конструктор копирования в бинарном дереве?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Приступая к работе над копированием или перемещением, в первую очередь нужно усвоить правило 0/3/5.
    Вот еще сылка на гайдлайн в дополнение.

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

    Но если тебе нужно руками определить конструктор копирования, тебе нужно определить и оператор копирования, и деструктор тоже.
    В этом суть правило трех.

    Если же тебе требуется определить конструктор перемещения, то тебе требуется следовать правилу трех, а в добавок еще определить и оператор перемещения.
    В этом суть правила пяти.

    Теперь к тому, как же тебе все-таки сделать правильное копирование. Именно твой случай является таким, когда копирование требуется переопределить. Твой конструктор копирования будет выражаться через клонирование.
    Первое что нужно сделать - это следовать правилу трех для ноды. Да, для нее надо определить конструктор копирования, оператор копирования и деструктор. В конструкторе копирования нужно клонировать буферы строк: т.е. снова выделить память и копировать в нее содержимое буфера исходного узла.

    Оператор копирования узла лучше сделать так:
    inline node& operator = ( const node& other )
    {
    	using std::swap;
    	
    	node temporary{ other };
    	swap( *this, temporary );
    	
    	return *this;
    }

    Это называется: Copy and Swap.

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

    Попутно стоит отказаться от использования голых указателей. Использую вместо них std::unique_ptr [?], который поможет тебе управлять временем жизни объектов в динамической памяти. Читай что тебе лучше каждый голый указатель заменить на std::unique_ptr.
    Ответ написан
  • Какие требования предъявляются разработчику на с++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    $1.5k/mth - это довольно серьезная оплата для специалиста. Такую з/п начислят линейному разработчику (Middle Engineer). В области работы с C++ все очень индивидуально и требования сильно пляшут даже не между компаниями, а между командами и проектами внутри компаний. Однако, средний набор требований выделить все-таки можно.

    За эти деньги от тебя потребуется глубокое знание стандарта C++ и способность быстро восполнить любой пробел в своей компетентности. Тебя допустят к ревью младших сотрудников, где от тебя будет требоваться, в первую очередь, отлов глубоких нарушений стандарта. Например, таких как нарушение алиасинга, нарушение передачи ресурса, нарушение времени жизни, потенциальные утечки ресурсов и памяти, потенциальное или явное неопределенное поведение. Также от тебя потребуется детальное понимание различий между версиями стандарта.
    От твоей экспертизы в стандарте будет зависеть скорость реализации функционала. Не только от одного тебя тебя, но и от тебя тоже.
    Это - твой Hard skill requirement. Алгоритмическая база и экспертиза в математике тоже имеют свой вес в профессиональных навыках. Но без глубокого знания стандарта, будь у тебя хоть Нобелевка по математике, толку на роли m.e. с тебя будет мало.

    И это - всего одна треть требований к тебе на такой позиции.

    Второй третью требований к тебе будут хорошо развитые социальные навыки. Это 10 лет назад при собеседовании смотрели только на твои профессиональные навыки. Сегодня профессиональные навыки иногда ставят на второе место перед навыками социальными.
    Каким бы крутым специалистом ты ни был, если ты токсичен, несдержан или неадекватен - твое появление в команде будет нежелательным. Бывают разные случаи. Бывает, адекватный человек заходит в команду токсиков и берет все в свои руки. Бывает, один свеженабранный неадекват уничтожает команду и продукт за период испытательного срока.
    Когда тебя допускают к ревью других людей, тебя самого тоже ставят на ревью, но уже в другой плоскости. Если твои слова будут негативно влиять на других сотрудников, к тебе появятся вопросы социального плана.
    Это - твой Soft skill requirement.

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

    Не стоит сегодня надеяться на то, что лишь за способность писать код тебе будут платить больше $800/mth. Хотя, конечно же, на нашем рынке труда существуют и на такое готовые предприятия.
    Ответ написан
  • Что не так с работой unordered_map?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что контейнер, или умный указатель, в современном C++ может (и делает это) несколько раз сменить аллоцируемый тип шаблонного аллокатора. При этом, оригинальный аллокатор, переданный через конструктор контейнера, будет использован для конструирования нового аллокатора с новым типом. Это означает, что новый аллокатор конструируется не через конструктор копирования, а через конструктор преобразования.

    Если в шаблоне алокатора не описано правило смены аллоцируемого типа (A::template rebind<U>::other), по умолчанию при смене аллоцируемого типа будет заменен первый шаблонный параметр. Был у нас Alloc<Foo, ...>, станет Alloc<Bar, ...>.
    Это означает что для правильной передачи состояния аллокатора нужно предусмотреть конструктор преобразования из аллокатора от твоего шаблона, но с другим первым аргументом.

    Еще можно использовать Polymorphic Allocator, но для этого потребуется сменить стандарт на C++17.
    Эту стратегию не так просто описать, поэтому я прибегну к ссылкам на доклады по этой теме.

    CppCon 2017: Bob Steagall “How to Write a Custom A...
    051. Modern C++ Allocators – Руслан Арутюнян (Intel)
    Taming dynamic memory - An introduction to custom ...
    C++Now 2018: David Sankel “C++17's std::pmr Comes ...
    Это, конечно, далеко не всё на данную тему. Но цели выписывать все у меня и нет. Я привел ссылки, которым доверяю в плане чистоты информации.
    Советую просто пройтись по хабру и ytube поиском докладов и статей.

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

    Но если ты работаешь со стандартом до C++11, то у тебя аллокатор вообще не может иметь состояние.
    All custom allocators also must be stateless. (until C++11)
    Ответ написан
  • Большие шаблонные классы( реализуя CRTP). Все пихать в один .h файл?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Сегодня с помощью шаблонов делается очень много предварительных вычислений на этапе компиляции, чтобы не делать эти вычисления вручную и не проводить их на этапе исполнения. Не всегда такие шаблоны могут похвастаться компактностью кода.
    Прежде всего, шаблоны служат для того чтобы автоматизировать генерацию кода. Не всегда требующий генерации код будет именно компактным или простым. Любой умный указатель или стандартный контейнер легко выбьется за пределы обозначенных 400 строк лишь только своим телом. А если учесть еще и команду поддержки такого стандартного шаблона, то там счет пойдет уже на тысячи строк.

    Важно понимать вот что. Код генерировать умеют не только шаблоны, но и препроцессор. Препроцессор работает на 4 стадии трансляции, где весь новый код генерируется еще на уровне блоков памяти с текстом.
    Инстанцирование шаблонов происходит уже на 8й стадии, где код генерируется уже более мелкими узлами абстрактного синтаксического дерева. Объем кода самого шаблона в этом случае практически ничего не портит. Однако, инстанцирование каждого шаблона происходит отдельно в каждом требующем инстанцирования модуле трансляции.

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

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

    По поводу организации кода.
    Код шаблонов точно так же можно организовать в наборы файлов. Для шаблонов точно так же разрешена и дружественность, и предварительное объявление, и определение не по месту объявления.
    Реализацию шаблонов можно развести между заголовками (.h-файлами) и файлами встраиваемых реализаций (.inl-файлами).
    При этом важным остается правило доступности шаблона из места его инстанцирования. Т.е. вся группа файлов с реализацией шаблона должна быть как-либо связана с заголовком объявления шаблона, который далее планируется предоставлять пользователю шаблона.
    Ответ написан