• Почему дружественная функция, определённая внутри класса с первым параметром встроенного типа, недоступна вне определения класса?

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

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

    Функция f5 не просто первично объявлена, она и определена по месту объявления дружественности. Ее имя является однозначно неквалифицированным в следствии своего определения.

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

    Все потому что т.н. имена скрытых друзей могут быть найдены только средствами ADL.
    Если коротко, Argument-Dependent Lookup опирается на типы аргументов при вызове функции, пространства их имен и пространства имен, в которых эти типы объявлены.
    ADL не выполняет поиск в пространствах имен в отношении фундаментальных типов. Поэтому код f5(5); буквально обречен на ошибку трансляции.

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

    В результате.
    Чтобы ADL нашел функцию f5, среди ее параметров обязан быть параметр и с типом A.
    Чтобы UNL или QNL смогли найти функцию f5, ее надо дополнительно объявить за пределами типа A в его пространстве имен.
    Ответ написан
    1 комментарий
  • Как создать окно OpenGL в окне полученном от CreateWindowEx (WINAPI)?

    @MarkusD
    все время мелю чепуху :)
    Использование glfw нередко приводит к тому, что обычные для OpenGL вещи средствами glfw сделать невозможно.
    В рамках glfw контекст OpenGL непосредственно объединен с окном операционной системы в типе GLFWwindow[?].
    Разделить их или использовать по отдельности уже не получится т.к. glfw не дает такого интерфейса.
    Так же, в функцию glfwCreateWindow[?] невозможно передать и дескриптор окна операционной системы если хочется создать дочернее окно в своем.

    Это такая ловушка использования glfw. Или пользователь использует glfw полностью с самого начала и мирится с ограничениями, или он отказывается от использования glfw и делает все руками.

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

    Для решения задачи более гибкого создания окон и рисования в них средствами OpenGL лучше зайти со стороны WGL[?] и прямой работы с Win32 API. В этом случае можно даже одним контекстом обойтись, привязывая его то к одному окну, то к другому на время отрисовки.

    Но если требуется просто добавить у себя элементы интерфейса в свое текущее окно, то лучше и удобнее будет взять imgui или подобную библиотеку. Для этого от glfw можно не отказываться.
    Ответ написан
    Комментировать
  • Как объяснить данный фрагмент кода?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В данном коде демонстрируется типичная ошибка подмены типа (type punning [?]), ведущая к неминуемому UB [?] по стандарту.

    num в данном случае представляет собой шестнадцатеричное число, при выводе оно переводится в десятичное число типа double.


    Вот что стандарт говорит о подмене типа.
    [basic.lval#11]
    If a program attempts to access the stored value of an object through a glvalue whose type is not similar to one of the following types the behavior is undefined:
    (11.1) -- the dynamic type of the object,
    (11.2) -- a type that is the signed or unsigned type corresponding to the dynamic type of the object, or
    (11.3) -- a char, unsigned char, or std​::​byte type.

    Относительно unsigned long long тип double не обладает ни одним из требуемых стандартом свойств. Отсюда прямой вывод что данный код вносит UB.
    Поэтому под большим вопросом остается то, что выводится данным кодом.

    В итоге, правильным примером подмены типа на другой может быть только пример, где используется подмена на разрешенный тип представления объекта: char, unsigned char или std::byte для C++17.
    C++ Core Guidelines в секции C.183 однозначно не советует для подмены использовать union. Этого действительно не стоит делать даже при условии того, что все современные трансляторы понимают такие конструкции так, как подозревает писатель.

    Почему неверно работает, к примеру, такой фрагмент кода:

    Второй блок кода означает приведение значения из типа unsigned long long в тип double. В этом случае производится именно стандартное преобразование типа, а не подмена с целью интерпретации представления памяти num иным образом.
    0xC068C0C000000000 для типа unsigned long long означает 13864543383726325760, при преобразовании этого значения в тип double значение будет преобразовано в 1.3864543383726326e+19. В таком виде оно и будет выведено.

    Относительно первого блока кода, лучшим примером подмены типа для кода из вопроса будет такой:
    unsigned long long num = 0xC068C0C000000000;
    double representation = {};
    memcpy( &representation, &num, sizeof( num ) );
    cout << representation;

    И не стоит бояться обращения к memcpy тут. Данный код полностью соответствует стандарту и ожидаемо оптимизируется транслятором до желаемого результата.

    Мне не понятно, как работает такая конструкция, т.е зачем передавать именно ссылку на переменную num?

    Иногда требуется произвести приведение типа значения [?], иногда требуется произвести интерпретацию памяти этого значения другим типом. Это и называется подменой типа или алиасингом. Более детальную информацию о подмене типа, и алиасинге вообще, можно узнать из этой заметки.
    Ответ написан
    Комментировать
  • Что значит С++17 в вакансиях? Зачем это пишут?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В вакансиях много чего пишут и нередко мало понимают что пишут. Номер стандарта сегодня является некоторым атрибутом хайпа, которым очередная нерадивая компания буквально обязана блеснуть ради привлечения увлеченного энтузиаста к круглогодичному ковырянию своего легаси на C98 вперемешку с шаблончиками из C++03.
    Хотя, безусловно, есть и высококлассные коллективы с передовыми знаниями и актуальными стандартами качества, где работать - сплошная мечта.

    Между тем, понимание конкретного стандарта языка означает не только знание этого стандарта, но и знание всех стандартов до него, и понимание различий между ними всеми. А так же - понимание цены перехода в коде от более старого стандарта к более новому.
    Понимание C++17, прежде всего, означает понимание цены перехода с C++14 в коде с auto и инициализацией списками, понимание цены отказа от std::tie в пользу структурного связывания (не говоря уже о понимании семантики структурного связывания) и понимание рациональности перехода на структурное связывание в конкретном месте кода, понимание последствий использования автоматического выведения аргументов шаблона типа в месте вызова конструктора и возможные последствия применения этого подхода в конкретном участке кода, понимание семантики работы с this в замыкании лямбды и отличия этого поведения в более старых стандартах. И так далее.
    Ответ написан
    Комментировать
  • Как в OpenGL провести разноцветную ломаную линию без градиента?

    @MarkusD
    все время мелю чепуху :)
    А ответ будет очень простым.
    В точке смены цвета тебе надо поставить две вершины. Первой вершиной надо закончить линию прошлого цвета, а второй - начать линию следующего цвета.
    На производительности это никак не скажется.
    Ответ написан
    2 комментария
  • Как сделать так чтобы функция принимала неограниченное число аргументов разного типа?

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

    template< typename... TArguments >
    void Foo( const TArguments&... arguments )
    {
    	static_assert( sizeof...( TArguments ) > 0, "This function shall not be called without arguments." );
    }


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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Как показало обсуждение в комментариях, исключение бросается из функции glTexImage2D.
    Подозрение в этом случае может пасть только на то, что размер переданной через data памяти меньше расчетного размера, который определяет функция исходя из параметров width, height, GL_RGBA, GL_UNSIGNED_BYTE.

    Это может означать что переданные в glTexImage2D параметры не соответствуют считанному изображению. А т.к. width и height получены напрямую при чтении изображения, вопросы возникать могут только относительно заявленного формата изображения - GL_RGBA, GL_UNSIGNED_BYTE.

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

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

    @MarkusD
    все время мелю чепуху :)
    Коротко о разрушаемости в Noita излагается в презентации разработчиков на GDC.
    Детальное описание разрушаемости в Jelly in the sky от автора игры: [1], [2], [3].

    В Червяках же реализация разрушаемости довольно простая.
    Мат. модель уровня состоит из битовой матрицы (где поднятый бит является заполненным, а снятый - пустым), и набора функций рисования в этой матрице. В этой битовой матрице изначально генерируется уровень и эта битовая матрица модифицируется в процессе игры. Функции рисования являются стандартными - это рисование линии от точки и до точки с заданной шириной, а так же рисование залитой окружности. Само рисование происходит нулевыми битами.
    По своей сути Червяки являются таким замысловатым редактором для рисования.
    Ответ написан
    4 комментария
  • Какой код правильнее?

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

    Вопросов к первому варианту нет. Тут сразу и наглядно видно агрегатную инициализацию, т.к. тип Book является агрегатным.

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

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

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

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

    Как производят интеграцию. Например - так, так, так, так, так или вот так.
    После интеграции скриптового движка в свой проект, функциональность своего проекта можно прокинуть на сторону скриптов используя непосредственно API скриптового движка.

    Одним из критериев выбора скриптового движка является его производительность. Чтобы не занимать специалистов подобной рутиной, когда-то давно уже были проведены замеры версий различных скриптовых движков. Результаты замеров доступны всем желающим.
    Однако, стоит напомнить, что не всегда самое быстрое решение является самым оптимальным.
    Ответ написан
    1 комментарий
  • Как организовать Tween систему в рамках ECS (entt)?

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

    Почему дизайн с одним компонентом обречен. Каждая система в ECS должна работать с выборкой по компонентам. В EnTT это делается с помощью представления потока компонентов - entt::registry::view.
    Такое представление оптимальным образом организует последовательность сущностей, в которых точно присутствуют обозначенные в представлении компоненты. Если начать проверять наличие у сущности иных компонент, помимо того что это противоречит концепции ECS, это затронет выборку из памяти за пределами отображения и сильно снизит производительность системы. Поэтому системе не стоит работать с компонентами за пределами своей выборки.

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

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

    Функции интерполяции у тебя всегда чистые, т.е. зависят только от переданных им аргументов. Для любого типа данных, от скаляра и до матрицы, можно определить функцию интерполяции между двумя значениями. Это все - внешние относительно ECS вещи, а внутри ECS на каждый ***InterpolationComponent у тебя будет своя система, которая делает выборку по целевому компоненту и по компоненту интерполируемости. Таким образом, уже на стадии выборки компонентов у тебя останутся только подходящие сущности, а все остальные - отфильтруются.
    Заметить стоит еще то, что такой подход позволяет одно конкретное значение интерполировать только одной функцией за раз. Если для одного значения требуется несколько одновременных интерполяций, такой подход уже не подходит. С другой стороны, для того чтобы правильно свести несколько таких анимаций для одного значения, требуется значительно более сложная система, чем просто функция интерполяции.
    Ответ написан
    2 комментария
  • Как избавиться от warning C26812: Enum type VkResult is unscoped?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Особенностью нестрогих перечислений является то, что все значения перечисления являются глобальными целочисленными константами подлежащего типа. Подлежащим по умолчанию является тип int.
    Таким образом, вместо VkResult result можно написать int result в виду того, что перечисление VkResult является нестрогим.

    С другой стороны, C26812 [?] относится к разделу Enum.3 из C++ Core Guidelines [?] и напрямую указывает на место определения нестрогого перечисления. К строке VkResult result = оно отношения не имеет.

    Дополнительно. #pragma warning(push, 0) [?] не делает того, что ты от него ожидаешь.
    Предупреждения подавлять стоит таким образом.
    #pragma warning( push )
    #pragma warning( disable: 26812 )
    #include <GLFW/glfw3.h>
    #pragma warning( pop )
    Ответ написан
    Комментировать
  • Почему работает const, а не constexpr для char*?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что указанный слева квалификатор const относится к правой половине спецификации типа до первого модификатора.
    const char& - ссылка на константный символ. Квалификатор - const, модификатор - &.
    const char* - указатель на память константного символа. Квалификатор - const, модификатор - *.

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

    И при чем же здесь constexpr? Просто constexpr всегда относится только ко всей спецификации типа со всеми модификаторами.
    const char* - указатель на память константного символа.
    constexpr char* - константный указатель времени компиляции на память символа. Тут нет ошибки, память символа тут считается модифицируемой.

    И если объект с типом constexpr char* получит характеристику ODR-used [?], то после трансляции кода это будет уже объект с типом char* const. Вот так.
    В то же время, строковые литералы имеют тип const char[N], т.е. статически определенный массив константных символов. Такой тип можно привести только к типу const char*.

    В результате, чтобы правильно определить константный указатель времени компиляции на память константного символа, нужно тип определить как constexpr const char*.
    И const в этом месте никакого прямого отношения к constexpr не имеет.
    Ответ написан
    Комментировать
  • Разработка игр для андроид на языке С++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    C++ не является полноценно "родным" языком для Android. Но для этой платформы можно вести разработку на C++.
    Такие движки, как cocos2d-x [?], Urho 3D [?], Unreal Engine 4 [?], Godot Engine [?] и, буквально, море менее известных проектов, позволяют вести разработку игр с использованием языка C++.

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

    Если использование C++ для тебя является важным, о Unity можно забыть. Это взаимоисключающие инструменты.
    И это не важно. Тот же Urho 3D позиционируется как Open Source Unity. Godot Engine обладает не меньшей свободой и гибкостью, но предлагает больше.
    Ответ написан
  • Variadic template, как перебрать в шаблоне все типы по очереди?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Пусть у нас есть некий аксессор:
    template< typename TComponent >
    TComponent* const QueryComponent();


    Пусть у нас есть, для простоты, некоторый обработчик:
    void PerformWorkflow( A*, B*, C* );
    Тут A, B и C - это типы компонентов.

    Чтобы подружить обработчик с аксессором необходимых ему компонентов, понадобится самый простой Parameter pack expansion.
    template< typename... TComponents >
    void ApplyComponents( void (*workflow)( TComponents*... ) )
    {
    	workflow( QueryComponent<TComponents>()... );
    }

    Здесь можно посмотреть как работает пример этого кода.

    В комментарии я вижу несоответствие типа параметра функции для each и типа результата get. Стоит напомнить что разыменование nullptr, который может вернуть get, приводит к неопределенному поведению [?].
    Поэтому, если распаковку переписать на *QueryComponent<TComponents>()..., т.е. на лету разыменовывать полученные указатели, то перед запуском функции для твоей entity следует убедиться что у нее есть все необходимые компоненты. Такая проверка является необходимой для запуска систем в ECS, т.к. система должна обрабатывать только удовлетворяющие своим фильтрам сущности.

    Для этого уже потребуется использовать Fold expression из C++17.
    template <typename... T>
    void each(typename identity<std::function<void(T& ...)>>::type func)
    {
    	for(auto &entity: m_entities)
    	{
    		if( !( (entity.HasComponent<T>() && ...) ) )
    		{
    			continue;
    		}
    		
    		func( *entity.get<T>()... );
    	}
    }
    Ответ написан
    1 комментарий
  • Как связать игровое клеточное поле в Entity Component System?

    @MarkusD
    все время мелю чепуху :)
    Вообще, пространство игрового поля не очень хорошо выражать в терминах ECS. Игровое поле - это, как правило, монолитный участок логики, для которого лучше подошел бы подход CBSE(COP). Обычно игровое поле представляется композицией нескольких пространств, в которых работают механики игры. Такие пространства удобнее выражать компонентами из CBSE, а не компонентами из ECS.

    Однако, не смотря на это все, есть хороший способ реализации игрового поля терминами ECS. Особенно если игровое поле дискретно.
    Чтобы начать думать в этом направлении, сперва требуется изучить клеточные автоматы: [1], [2].
    Когда с принципами работы клеточных автоматов станет понятнее, стоит обратить свое внимание на шаблон инверсии контроля. При прямом контроле всем твоим миром руководит квант времени, который спускается с главного цикла до каждой сущности. Такой подход неприемлем на игровом поле в терминах ECS.

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

    И теперь самое интересное. Игровые сущности теперь тоже являются компонентами игрового поля. И за счет все той же инверсии контроля игровые сущности теперь обновляться могут только для возбужденных клеток поля. Если клетка спокойна, стоящий на ней персонаж не получает квант обновления. Это - важное правило, которое требуется соблюдать чтобы не нарушать целостность реализации игрового поля через ECS.
    Любое управление персонажем может только возбуждать клетку игрового поля, где стоит персонаж. Когда персонаж перемещается в другую клетку, между клетками переходит не сам персонаж, а его компоненты. Буквально, сперва делается перемещение данных компонентов персонажа в сущность целевой клетки, а потом происходит удаление компонентов персонажа в текущей клетке.

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

    Через такой подход вполне возможно реализовать что-нибудь вроде Factorio.
    Ответ написан
    Комментировать
  • Инициализация элемента к нулю?

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

    В контексте вопроса полезно будет рассмотреть два отдельных вида инициализации: инициализацию по умолчанию и инициализацию значением.

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

    Интереснее будет рассмотреть поведение инициализации значением.
    Форма T() может привести к вызову конструктора с пустым std::initializer_list, к вызову конструктора по умолчанию, а может свестись к инициализации по умолчанию в том случае, если конструктор по умолчанию удален или по своим причинам пропущен в пользовательском типе.
    Каждый, кроме последних двух, пункт поведения инициализации значением оперирует словом class, т.е. имеет отношение только к сложным типам. Последний пункт описывает поведение инициализации для всех неописанных выше случаев как инициализацию нулем.
    И именно инициализация нулем написана в примере вопроса.

    Форма T() хорошо подходит для инициализации локальных временных объектов, а для инициализации именованных объектов лучше подходит форма T object {};.
    Форма T object = T();, на самом деле, выполняет инициализацию копией, где в качестве копии используется локальный временный объект T().

    В результате, конкретно конструкция T() или T{} может привести к вызову конструктора по умолчанию или вызову конструктора от пустого std::initializer_list для сложного типа и инициализацию нулем для простого типа.
    Это - очень полезное поведение в метапрограммировании, где у тебя может быть шаблон типа, в котором требуется по значению разместить объект с типом параметра шаблона. Этот объект требуется инициализировать правильным образом, но ты никак не можешь классифицировать тип, передаваемый параметром шаблона. Благодаря инициализации значением можно не задаваться вопросами природы типа из шаблонного параметра.

    Но увлекаться этим подходом тоже не стоит. Я предпочту вариант std::string string; варианту std::string string = std::string(); потому что для std::string инициализация по умолчанию сделает точно то же самое, будет эффективнее (т.к. во втором случае будет копирование) и будет понятнее.
    Код HRESULT res = HRESULT(); стоит заменить на инициализацию одним из базовых значений. Например - кодом S_OK. Это создаст понимание базового состояния программы в месте определения переменной. Тип HRESULT имеет очень сильную семантику и сразу о многом говорит подготовленному читателю.
    Только HWND window = HWND(); или HWND window{}; на самом деле смотрится к месту, т.к. в WinAPI нет предопределенного начального значения и объект проще провести через стандартную инициализацию значением. Уж этот краевой случай разработчики WinAPI должны предусмотреть.
    Ответ написан
    1 комментарий
  • Как организовать структуру игры Монополия на ECS?

    @MarkusD
    все время мелю чепуху :)
    Самой главной ошибкой начинающих работать с ECS людей является попытка засунуть в ECS сразу и вообще всё.

    У тебя игра по типу монополии. Что есть в монополии?
    В игре заявлены игроки, дискретное поле, активы и события. Еще могут быть заявлены дополнительные правила.
    Игроки и активы имеют свои эффекты и могут быть деактивированы событиями. Собственно, это и есть сущности игрового ECS-мира.

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

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

    По своей сути, в монополии ECS будет выполнять задачу документооборота между сущностями. Добавить компонент владения, убрать компонент владения, зачислить деньги, списать деньги, добавить компонент активности, убрать компонент активности. ECS управляет только общими свойствами между полностью обезличенными сущностями, где между игроком и купленным им активом нет фактической разницы.
    Механизм перевода хода тоже является полностью отдельным от ECS, он может только опираться на набор компонент игроков и данные компонент.
    Ответ написан
    3 комментария
  • Entity Component System + ООП подход?

    @MarkusD
    все время мелю чепуху :)
    ECS является подходом декомпозиции не только логики и данных, но и подходом отказа от агрегации и наследования в пользу косвенной композиции. Явной композиции в ECS тоже не предусматривается, а косвенная композиция не намекает на связность или структурированность данных. Косвенная композиция обозначает только наличие каких либо данных по некоторым косвенным признакам.
    Более того, в рамках ECS сторонний код и с данными нормально работать не способен. Для стороннего кода в ECS предусматривается уровень абстракции для чтения и записи данных.

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

    К примеру есть сущность игрок с набором компонентов наследованная от Entity,

    Нет, это сразу будет не ECS. Сущность игрок - это и есть entity, являющаяся эфемерной единицей в мире ECS.
    И методов у entity быть не может. Вся логика сущностей распределена по системам и сильно зависит от имеющихся у сущности компонент.
    Более того. Если ты говоришь о том, что в твоем мире может существовать сущность с характеристикой игрока, таких сущностей в твоем мире может быть до 100% всего ECS мира. Если игрок у тебя должен быть представлен единичной сущностью, значит это уже не сущность, а внешний объект, по ошибке введенный в ECS мир.

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

    Про состояния, пустые компоненты добавлять удалять в entity как флаги что сущность в каком либо состоянии, и отдельные системы для обработки каждого состояния это нормальный подход ?!

    Битовые компоненты - это рядовая оптимизация для хранения пустых компонент. Да, так делают и обеспечивают правильную работу систем, чтобы фильтры компонент одинаково обрабатывали как полноразмерные компоненты, так и битовые тоже.
    Ответ написан
  • Лучшие источники для изучения CPP?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В самую первую очередь - это будет документация языка. Ее очень удобно использовать как справочник. Это - твой самый первый источник информации по любому вопросу.
    isocpp поддерживается создателем языка и содержит море полезной информации.
    C++ Core Guidelines является манифестом пользователя C++. Его знать обязательно. Документ регулярно дополняется.

    More C++ Idioms. Шаблоны проектирования имеют свою собственную многомерную классификацию. Идиомы - это функциональные шаблоны проектирования, применимые, как правило, или для конкретного языка, или для некоторого семейства языков. Эта открытая книга помогает ориентироваться в некотором начальном наборе идиом конкретно для языка C++.
    C++ Patterns - еще один полезный ресурс для изучения применимых к C++ шаблонов проектирования.
    С Fluent C++ ты уже знаком.
    Безусловно, блог создателей PVS-Studio.
    Habr, конечно же.
    Блогов очень много, их можно просто найти по релевантной фразе "C++ blog".

    Помимо этого есть большое количество каналов от разных конференций, доклады на которых всегда помогают понять язык лучше.
    С++Russia,
    C++Now,
    Pacific C++,
    CppCon,
    code::dive,
    Meeting C++.

    Так же будет полезно изучить книги авторов:
    Андрея Александреску,
    Герба Саттера,
    Девида Вандервуда,
    Скотта Мейерса,
    Роберта Мартина.
    Есть и другие очень полезные авторы. Тут у меня, пожалуй, только самый основной список.

    Последим, и самым важным, источником будет текущая рабочая версия стандарта языка, а так же пара лабораторий для практики: Compiler Explorer и C++ Insights.
    Ответ написан
    Комментировать