Задать вопрос
  • Почему статическая переменная инициализируется дважды?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Первое, с чего стоит начать ответ: Порядок инициализации глобальных данных программы стандартом не определен.
    Поэтому нельзя говорить о том, какие конструкторы после каких вызываются. Сейчас это так, а завтра поменяется.
    Описанный у тебя случай - это Static Initialization Order Fiasco.

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

    Как с этим справляться? Отец-основатель уже очень давно все рассказал.
    Нужно просто воспользоваться стандартом языка. Тем, что стандарт предписывает инициализировать статические данные внутри функции при первом ее вызове.
    Ответ написан
    Комментировать
  • Системы автоматизации задач сборки?

    @MarkusD
    все время мелю чепуху :)
    Это очень хорошо что тебя корежить начало от зоопарка скриптов.

    Систем управления задачами довольно много. Они есть на разных языках. Те же MSBuild, cmake, Gradle, Phing, Ant, Zig и.т.д. являются подмножеством множества систем управления задачами. Поэтому нередко люди берут тотже cmake для решения описанных у тебя в вопросе задач. cmake для решения подобных задач не подходит в силу своей организации и изначального предназначения, но люди обычно плачут, колются и продолжают сидеть на кактусе.

    Между тем, есть целый класс конкретно систем управления задачами. Такие системы предоставляют только инструменты менеджмента, но не дают никакого начального набора задач, в отличие от того же cmake/Gradle/...
    К таким системам менеджмента задач сразу и легко можно отнести Doit, Schedule и их аналоги.

    Я работаю с Doit уже больше 10 лет, периодически помогаю его развивать и всячески содействую его разработке.
    Для меня Doit стал решением всех описанных у тебя задач и является решением огромного числа неописанных у тебя задач автоматизации и сопровождения процесса разработки проектов.
    Doit работает быстро, эффективно, умеет пропускать задачи по условиям инкрементальности, имеет очень гибкую систему настройки поведения и, что главное, от начала и до конца весь написан на Python - одном единственном языке. Он легко интегрируется в Jenkins и позволяет полностью автоматизировать подавляющую часть задач поддержки разработки. Эта система управления задачами является самой гибкой и успешно приспосабливается к самым разным задачам автоматизации ручного труда.

    Только не надо пытаться пробовать заменить абсолютно всё на что-то одно. Первое правило кроссплатформенной разработки: Под каждую платформу сборка должна быть максимально нативной. Это значит что в процессах есть нативные и сторонние подходы. Крайне желательно чтобы для целевой платформы все делалось максимально нативными методами. А система управления задачами должна только запускать это всё, как рядовую задачу.
    Ответ написан
    Комментировать
  • Как написать прикладной протокол?

    @MarkusD
    все время мелю чепуху :)
    Первое что стоит понять: модель OSI не является строгой и не ограничивает разработчиков своими рамками.
    Например, верно, стек TCP/IP задает самостоятельную модель. Делается это через отождествление групп уровней OSI на уровни TCP/IP.

    Этап следующий. 4-й уровень модели TCP/IP эквивалентен группе из 5-7 уровней OSI. Что это означает.
    Базируясь на некотором транспортном протоколе 4-го уровня OSI, тебе потребуется поднять 5-й, 6-й и 7й уровни самостоятельно.

    На 7-м уровне у тебя должен быть прикладной код, с которым клиент твоего протокола будет общаться через DTO/VO.
    На 6-м уровне должна обеспечиваться трансляция данных между DTO/VO 7-го уровня и JSON для передачи по сети.
    На 5-м уровне нужно обеспечить гарантию сессии через проверку и внедрение в трафик маркера сессии, т.е. того самого ABC.

    Разведи свой код на такие уровни и тебе все станет ясно.
    Ответ написан
    1 комментарий
  • Как начать программировать с использованием DirectX?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    ISBN 978-1-56881-720-0
    Jason Zink, Matt Pettineo, Jack Hoxley: "Practical rendering and computation with Direct3D 11".

    На русском книги нет. Книга легко читается и в оригинале. Уже с первых глав дает понимание современного пайплайна графики, стадий инициализации систем DirectX 11, способов загрузки и представления в памяти требуемых для отрисовки данных. В книге хорошо описан и язык HLSL.
    Книга хорошо подходит для холодного старта в работе с DirectX 11.
    Ответ написан
    Комментировать
  • Какие есть доки по сборке APK/AAB с использованием Android NDK?

    @MarkusD
    все время мелю чепуху :)
    Для изучения всего пайплайна сборки Android-приложения достаточно просто прочитать официальную документацию.
    Сборка кода низкого уровня выполняется через ndk-build и cmake.
    Сборка APK выполняется через Gradle напрямую.

    Android Studio ничего от разработчика не скрывает. Gradle по умолчанию встраивается в каждый проект Android-приложения. Gradle всегда настраивается руками, прямо в сценариях самого проекта. Для сборки APK требуется только сам проект, SDK и NDK. Студия не требуется, Gradle запускается из консоли. Сборку низкого уровня можно как встроить отдельным этапом в сценарий Gradle, так и сделать выделенным этапом, также запуская cmake/ndk-build из консоли.
    И все это детально описано в документации разработчика по ссылкам выше.

    Практически все вопросы решаются или через примеры в SDK, или через примеры в NDK.
    Все продвинутые вопросы решаются или в документации Gradle-плагина для Android, или в документации к NDK-Build.
    Ответ написан
    2 комментария
  • Что стоит учить с или c++ или c#?

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

    Языки программирования - это инструменты. А инструменты всегда надо выбирать от условий задачи. У тебя не выйдет решать все задачи только каким-то одним языком. Знать во всех тонкостях и уметь использовать строго по назначению Python, Java, C#, C++, Lua, TypeScript и, например, PHP не просто нормально, а важно.
    Бьерн Страуструп один раз говорил о том, что для настоящего инженера важно знать порядка 5 разных языков и разбираться в их особенностях, чтобы считать себя настоящим специалистом.

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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для подобных целей уже давно заведены intptr_t и uintptr_t [?], а еще ptrdiff_t [?].
    Именно эти типы и стоит использовать для прямой работы с адресами.

    Теги C и C++ смешивать не совсем уместно. Это мешает выбору более подходящего варианта ответа.
    В C++, например, если нужно только хранить адрес и позволять с ним только определенные операции, лучше мог бы подойти enum class MemoryAddress : uintptr_t;. Пустое перечисление с достаточной шириной и выравниванием избавит от возможности случайно что-то куда-то прибавить или умножить, да и от неявных преобразований убережет. А перегрузка операторов поможет разрешить только определенные операции.
    Но в C так не получится.
    Ответ написан
    Комментировать
  • Ошибка при ручном высчитывании перспективной проекции и точки на экране?

    @MarkusD
    все время мелю чепуху :)
    Для начала тут нужно разобраться с пространствами.
    В каком пространстве рисуется серая платформа? В каком пространстве задана область обзора камеры для платформы? В каком пространстве задана область проекции камеры для платформы?
    В каком пространстве рисуется красный квадрат? В каком пространстве задана область обзора камеры для квадрата? В каком пространстве задана область проекции камеры для квадрата?

    А следом надо разобраться с порядком переходов между этими пространствами.
    У тебя используется два пространства мировых координат и две камеры, результат проекции которых сводится в одну поверхность презентации. Чтобы из точки мирового пространства плоскости получить точку мирового пространства квадрата, тебе нужно взять прямую матрицу WVP пространства плоскости и умножить ее на обратную матрицу WVP пространства квадрата.
    Результирующая матрица будет матрицей перехода из мирового пространства плоскости в мировое пространство квадрата. Умножая вершины плоскости на эту матрицу, ты будешь переводить их координаты в пространство, в котором тебе в этих вершинах надо рисовать красные квадраты.
    Ответ написан
    Комментировать
  • C++ CMake Как исправить ошибку?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тут проблема не в CMake и не в CLion. Это проблема MinGW и кодировки файлов с исходным кодом.

    Файлы сейчас сохранены в какой-то другой кодировке, когда GCC в составе MinGW по умолчанию ожидает кодировку UTF-8.
    Достаточно будет сконвертировать файлы в кодировку UTF-8 и MinGW начнет их переваривать.
    Ответ написан
    Комментировать
  • Что за странная запись в С++?

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

    Насколько видно, базово механизм делится на две ветви: поиск квалифицированных имен и поиск неквалифицированных имен.
    И именно в этот момент речь заходит о т.н. квалификации имени выражения.
    Квалификация имени выражения тем полнее, чем точнее от самого корневого пространства имен (т.е. от ::), через все пространства имен и пространства составных типов, написано имя выражения.

    Имя Process::WaitForExit, хоть и является уже квалифицированным за счет указания пространства типа, в котором метод объявлен, все еще остается недостаточно квалифицированным чтобы считаться полностью квалифицированным.
    Вызов метода по его полной квалификации выглядел бы так:
    process.::base::Process::WaitForExit(&exit_code);

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

    Для чего нужно было писать полную квалификацию конкретно в приведенном коде?
    А кто его знает. Метод base::Process::WaitForExit[?] не является виртуальным чтобы сделать предположение о невиртуальном вызове.
    Просто автору так захотелось, наверное.
    Ответ написан
    2 комментария
  • Как получить данные из буфера глубины при включенном multisample в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    Я конечно могу найти луч и потом искать пересечения со всеми объектами на сцене и рассчитывать координату z уже из этого, но мне кажется это слишком ресурсозатратно

    Однократное использование glReadPixels в сотни раз более ресурсозатратнее обычного оптимизированного алгоритма проверки на пересечение луча с объектами сцены.

    В разработке игр на мобильных устройствах алгоритм проверки пересечений запускается даже не на каждые 10 пикселей экрана, а просто постоянно в каждом кадре, пока игрок не поднимет палец с экрана. И у людей при этом проблем с производительностью нет даже на 96+ FPS.
    В продуктовых решениях мы никогда не используем glReadPixels в покадровых рутинах. Ни в мобильной разработке, ни в десктопной, ни под консоли.

    Вот ответ, который тебе поможет разобраться с переводом координат мышки в 3D пространство сцены.
    Чтобы снизить трудоемкость проверки на пересечение с объектами сцены, используй или Q-Tree, или Oc-Tree, или BSP, или Агломерацию.
    Чтобы снизить трудоемкость проверки на пересечение конкретного объекта, используй AABB, OOBB и все те же Q-Tree/Oc-Tree или BSP для отсечения лишних полигонов модели.

    Это все позволит определять объект сцены под мышкой значительно быстрее чем всего один вызов glReadPixels с проходом по матрице пикселей.
    Ответ написан
    1 комментарий
  • Ошибка в Visual studio opengl. Как решить?

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

    __imp_glClear и __imp_glDrawArrays - это стандартные функции OpenGL, определены они в библиотеке opengl32.lib, которую и требуется подключить как внешнюю зависимость к твоему проекту.

    Зависимости в проект подключаются через свойства проекта Visual Studio.
    Ответ написан
    5 комментариев
  • Почему в Java изменение интерфейса базового класса посредством модификации сигнатуры разрешено?

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

    cat1 -> sound();
    Тут по пунктам. Сперва UNL определит имя cat1 как переменную в локальном пространстве с типом Cat*.
    Далее UNL же определит что в пространстве класса Cat есть метод sound.
    Далее для определения перегрузки Cat::sound запустится ADL и найдет в пространстве только одну перегрузку - метод без параметров. ADL вернет объявление этой перегрузки, т.к. она удовлетворяет условиям вызова метода.

    Отдельно отметить стоит то, что это именно писатель определил в пространстве класса только одну перегрузку метода sound. Алгоритм ADL довольно строг и не будет искать объявления где-либо еще.
    Именно поэтому при данном определении класса Cat код cat1 -> sound(1); трансляцию никогда не пройдет. Просто потому что в пространстве класса написана только одна перегрузка метода.

    Что делать, когда перегрузки из родительского класса нужны все, но замещать их все в классе дочернем нужды нет?
    Тут на помощь приходит ключевое слово using[?].
    Это слово нужно использовать в контексте делегирования из пространства родительского класса. Определение класса Cat должно быть таким.
    class Cat : public Animal
    {
    public:
    	// Delegate all overloads of `sound`.
    	using Animal::sound;
    	
    	void sound () override
    	{
    		std::cout << "Cat.sound()" << '\n';
    	}
    };


    В этом случае код cat1 -> sound(1); пройдет трансляцию и приведет к вызову void Animal::sound(int i).
    Ответ написан
    2 комментария
  • Почему утверждается, что int32_t имеет ширину точно 32 бита, если он является всего лишь псевдонимом int, который может быть больше 32 бит??

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

    Да, стандарт утверждает что тип int может иметь размер не меньше 16 бит.
    Таблица имеет название "Minimum width", т.е. минимальный размер. Минимальный - это значит что int может иметь размер в 16 бит. А может иметь и 64 бита.

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

    Ошибка в рассуждениях заключается в том, что ты проводишь нить только от int32_t к int и дальше к стандарту. В то время как стандарт определяет и требования к типу int32_t тоже.
    Тип int32_t всегда и для любой модели памяти выбирается таким образом чтобы гарантировать размер в 32 бита.

    То что где-то int32_t является псевдонимом int - это не более чем временное совпадение.
    Ответ написан
    1 комментарий
  • Почему не работает метод clone для класса Test1?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Типом результата Test::clone является std::shared_ptr<Test>.
    Строчка Test1 asd = v[1]->clone(); эквивалентна строчке Test1 asd = std::shared_ptr<Test>{ ... };.
    Оператора или конструктора преобразования из std::shared_ptr<Test> у типа Test1 нет. Трансляцию строчка Test1 asd = v[1]->clone(); не пройдет.

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

    Когда ты заранее знаешь тип, тебе незачем пользоваться клонированием, потому что ты можешь просто скопировать стандартным способом.
    Правильно твоя строчка должна выглядеть так: std::shared_ptr<Test> asd = v[1]->clone();.
    Или так:
    Test1 asd{ *std::static_pointer_cast<Test1>( v[1] ) };
    .
    Ответ написан
    Комментировать
  • Использование шаблона в многофайловом проекте, как реализовано в vector например?

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

    Что это означает на практике. После 4й стадии трансляции формируется модуль трансляции, содержимое которого и компилируется в объектный файл. Код модуля трансляции должен быть исчерпывающим, в нем должны находиться все необходимые для компиляции определения. А определение шаблона относится именно к таким требуемым.
    Значит определение используемого в модуле шаблона должно быть внесено в модуль трансляции. А это значит что или определение должно быть вписано в тот .cpp файл, в котором шаблон инстанцируется, или определение должно быть добавлено через директиву #include.

    В первом случае, когда используемое в .cpp файле определение шаблона вписано в том же .cpp файле, определение шаблона будет достижимо только в данном .cpp файле и больше нигде. В этом случае попытка инстанцировать шаблон в другом модуле трансляции приведет к ошибке трансляции, т.к. там определние шаблона будет уже недостижимо.
    Файлы исходного кода включать через директиву #include - это дурной тон и прямой путь к нарушению ODR.

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

    Широкой практикой является разделение кода шаблонов на несколько типов файлов. В .h файлах обычно делают или объявления шаблонов функций, или определения шаблонов классов. В .hpp/.inl файлах обычно делают определения шаблонов функций и шаблонов методов.
    При этом .hpp/.inl файл очень часто включается в самом низу .h файла с его объявлениями.
    Моя личная рекомендация: использовать в таких случаях расширение .inl (от слова inline), т.к. для .hpp столь же широко закреплено взаимоисключающее с .h значение заголовка C++ кода. И видеть эти два расширения в одном проекте обычно бывает странно.

    Вектор же, например в проекте LLVM, реализован так, что часть определений в нем сделаны по месту объявления, а часть - как внешние определения сразу после определения шаблона вектора. Все это сделано прямо в одном заголовке.
    Ответ написан
    Комментировать
  • Актуальны ли книги Александреску, Майерса и Саттера?

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

    C++ развивается вот уже 38 лет. Новые стандарты сегодня приходят с достойной одобрения частотой, но начиная с C++11 изменения в стандартах до сих пор ничего кардинально не ломают. Даже новые возможности концептуально связаны с опытом прошлых стандартов.
    С другой стороны, трансляторы. Новые стандарты языка не приходят сразу, сперва требуется дождаться их поддержки в современных трансляторах. А это происходит не в одно время и не сразу по выходу нового стандарта. Да и когда появляется версия с поддержкой нового стандарта, эта поддержка не лишена ошибок, опознать которые способен только опытный инженер с экспертизой в новом стандарте.
    Разработка же и вовсе не поспевает за трендами. На собеседованиях я то и дело слышу как где-то кто-то еще только вчера и еще только решил перейти на C++11. В 23-м году.
    Самым широко используемым стандартом сейчас является C++17, большинство функций которого многими компаниями так до сих пор и не используется. Люди до сих пор еще только привыкают к нему.
    Книги представленных авторов в понятной форме передают читателю ценный базовый опыт, который можно применять вообще не привязываясь к стандарту языка. Главное - это не брать в рассмотрение книги до 2011 года.

    Чтобы быть на острие развития языка, нужно не книги читать, а быть сильным энтузиастом и иметь изначально глубокую экспертизу в стандартах языка. Авангард развивается за счет самостоятельных экспериментов и исследования пределов возможностей последних стандартов C++. Не за счет ожидания и чтения книг.
    Ответ написан
  • Почему доступ к элементам vector-а O(1)?

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

    Такое поведение имеет список, односвязный или двусвязный.

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

    Именно поэтому доступ к любому элементу вектора имеет сложность O(1). Это просто смещение на индекс элемента относительно начала блока памяти с помощью адресной арифметики.
    Ответ написан
    Комментировать
  • Для чего использование Hydrator pattern вместо создания объекта?

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

    Изначально гидратор являлся маркетинговым шаблоном в дизайне Hibernate ORM. Маркетинговым - это потому что нового ничего шаблон не вносит, просто имеющееся называет броским новым термином ради привлечения внимания. Шаблон дизайна - это потому что гидратор явно в коде не представлен, будучи именно высокоуровневым описанием поведения некоторого кода.
    И самое интересное в том, что маркетинговая задумка удалась, хоть и немного в другом смысле. Люди начали понимать гидратор по-своему. В результате часто получается так, что гидратором называются обычные билдер или фабрика, а то и вовсе сериализатор. Термин людям понравился, просто.

    Декоратор для гидратора из приведенного по ссылке кода - это просто еще одна попытка использовать любимый термин. Гидратор в этом коде гидратором не является по всем своим признакам. Это - сериализатор.
    Ответ написан
    Комментировать
  • Как сделать обработчик коллизий в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    OpenGL - Open Graphics Library.
    Это - открытая библиотека работы с графикой. И в ее API содержатся только функции работы с графикой.
    Для обработки коллизий нужна отдельная библиотека обработки коллизий, в зависимости от мировой системы координат. Простую библиотеку можно и самому сделать.
    Для обработки коллизий используют библиотеки коллизий и физические движки.
    Ответ написан
    3 комментария