• Как начинать в геймдеве?

    @MarkusD
    все время мелю чепуху :)
    Когда ты молодой и еще только учишься в школе, начинать в геймдеве проще простого!
    Держи дорожную карту: A Study Path for Game Programmer.

    Изучай на здоровье! :)
    Ответ написан
    2 комментария
  • Когда удаляются умные указатели?

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

    std::auto_ptr мог следить только за памятью одного экземпляра. Передать туда память массива можно, но деструктор будет вызван только у первого экземпляра массива. В общем смысле это означает утечку памяти.
    std::unique_ptr, наоборот, способен контролировать память как единичного экземпляра, так и массива экземпляров. Еще в отличии от своего устаревшего товарища, std::unique_ptr способен спокойно передавать свое состояние, не создавая возможность двойного освобождения памяти. В дополнение, std::unique_ptr еще способен пользоваться нестандартными деструкторами, что очень кстати при работе, например, с COM-объектами или нестандартной схемой аллокации памяти.

    Любой умный указатель является обычным объектом своего типа. Все объекты всех типов имеют свое время жизни, согласно условиям своего конструирования.
    Умные указатели разрушаются тогда, когда завершается их время жизни. Для умных указателей в глобальном пространстве время жизни завершается сразу после выхода из main.

    В языке нет термина "обычный массив", этот вопрос непонятен.
    Ответ написан
    Комментировать
  • Как конвертировать массив типа char в int?

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

    Начнем с void swap(T& first, T& second).
    Она просто не нужна т.к. есть std::swap.

    template <typename T1>
    void out_array(const T1* array, size_t arraySize)

    Параметр arraySize тоже не нужен. Ты работаешь с сырыми массивами фиксированной длины и можешь дать своему шаблону функции понимание длины такого массива. Делается это так.
    template < typename TElementType, size_t LENGTH >
    void out_array( const TElementType array[ LENGTH ] )


    Далее.
    char array2[n] = { '2','1','4','5','3','-3','-1','-2','-4','-5' };

    В этом месте сразу две ошибки. Первая - это инициализация символьными литералами. '2' не будет эквивалентно 2. С этого момента сортировка будет сортировать по коду символьной таблицы, а не по значению числа. Это ошибка, т.к. это явно не то поведение, которое ты ждешь.
    Вторая ошибка - это '-3' и.т.д. Тип у такой конструкции будет int, а не char. Компилятор тебе именно об этом и пытается сказать.

    int array3[n] = atoi(array2);
    Эту конструкцию сложно понять. Сказать можно одно - так массив фиксированной длины инициализировать не получится.
    Если этой конструкцией ты хотел описать инициализацию массива, то это можно попробовать сделать вот так:
    int array3[n];
    std::transform(
        std::begin( array2 ), std::end( array2 ), std::begin( array3 ),
        []( const char item ) -> int { return item; } 
    );

    Но это тоже скользкое решение. В твоем случае все будет нормально, в общем случае это плохое решение. Изучи документацию на функцию std::transform.
    Ответ написан
    Комментировать
  • Почему возникает эта ошибка?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Согласно 14му стандарту, у тебя есть два способа инициализации поля по месту объявления:
    Способ первый, принятый начиная с C++11:
    Node root = {T{}, nullptr, nullptr};
    Способ второй, принятый начиная с C++14:
    Node root{T{}, nullptr, nullptr};

    Твой текущий код инициализации поля синтаксически неверен.
    Ответ написан
    Комментировать
  • Как настроить локальное хранилище отладочных символов в Visual Studio?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В дереве настроек студии есть раздел "Debugging", там есть подраздел "Symbols". Среди настроек этого подраздела есть поле "Cache symbols in this directory" для указания пути, куда, собственно, и стоит сохранять загруженные отладочные символы.
    Ответ написан
    Комментировать
  • Почему не работает сдвиг влево на переменное кол-во байт?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Твой код содержит Undefined Behavior.
    In any case, if the value of the right operand is negative or is greater or equal to the number of bits in the promoted left operand, the behavior is undefined.

    Не стоит допускать сдвига на полное число бит типа левого операнда.
    Ответ написан
  • Что означают данные строки?

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

    template <typename T>
    struct NameOf {};

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

    #define DEF_TYPENAME(type) template <> \
    struct NameOf<type> {\
        static const char value[];\
    };\
    const char NameOf<type>::value[] = #type;

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

    DEF_TYPENAME(int)
    DEF_TYPENAME(double)
    DEF_TYPENAME(long double)
    DEF_TYPENAME(float)
    DEF_TYPENAME(char)
    DEF_TYPENAME(long)
    DEF_TYPENAME(unsigned)
    DEF_TYPENAME(unsigned long)

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

    template <typename T, typename ...types>
    void printTypes(T)
    {
        std::cout << NameOf<T>::value << std::endl;
    }
    
    template <typename T, typename ...types>
    void printTypes(T, types... t)
    {
        std::cout << NameOf<T>::value << ", ";
        printTypes(t...);
    }

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

    template <typename T, typename ...types>
    void printTypes(T, types... t)
    {
        std::cout << NameOf<T>::value << ", ";
        printTypes(t...);
    }

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

    В следствии оптимизации, хвостовая рекурсия развернется в плоскую последовательность обращений к std::cout << NameOf<T>::value << std::endl;.
    Ответ написан
  • Где хранится размер памяти, которая выделена указателю?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Начнем вот с этого: CppCon 2015: Andrei Alexandrescu “std::allocator I...

    На самом низком уровне память выделяется страницами или блоками. Принцип выделения зависит от особенностей организации ОС и самого железа. Освобождается память в точном соответствии с тем, как она была выделена.
    И обычный пользователь C++ этого ничего не видит.

    Оператор ::new является точкой обращения к аллокатору памяти процесса. К какому именно аллокатору идет обращение - как правило неизвестно. Это может быть dlmalloc, mimalloc или jemalloc. Их много и перечислять можно долго. Это может быть и самостоятельно созданный по мотивам доклада Александреску аллокатор.
    Каждый аллокатор, маркируя память как выделенную и передавая ее пользователю, каким-либо образом оформляет сервисную информацию о выделенном участке. Сервисная информация может храниться в заголовке, в памяти прямо перед отдаваемым пользователю адресом, может храниться в хвосте передаваемого пользователю участка памяти, а может храниться в совершенно отдельном пространстве памяти - в сервисном разделе.

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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Метод IMagicSpell::ShowDebugInfo у тебя обращается к приватному полю IMagicSpell::SpellInfo. Это поле недоступно для наследников, т.к. является приватным.
    Конструктор-же, Fireball::Fireball, у тебя работает с приватным полем Fireball::SpellInfo.
    Поля IMagicSpell::SpellInfo и Fireball::SpellInfo являются разными, расположены в разных областях памяти объекта Fireball и в принципе никак не могут пересечься своими данными.
    Размер твоего Fireball будет с два MagicSpellInfo плюс размер таблицы виртуальных символов.

    От причины можно перейти к достижению желаемого поведения.
    Самое главное - второй экземпляр MagicSpellInfo в иерархии - лишний. Fireball::SpellInfo является дублирующим, лишним полем, которое не позволяет добиться необходимой тебе функциональности. Ведь в обоих твоих классах тебе надо работать с одними данными, с полем IMagicSpell::SpellInfo.
    Просто удаляем Fireball::SpellInfo.
    Fireball
    class Fireball: public IMagicSpell {
      public:
        Fireball() {
          SpellInfo.Name = "Огненный шар";
          SpellInfo.Description = "Мощный огненный шар, сметающий все на своем пути.";
          SpellInfo.Type = fire;
          SpellInfo.MinDamage = 30;
          SpellInfo.MaxDamage = 50;
          SpellInfo.Range = 40;
          SpellInfo.Distance = 120;
        }
    
        virtual ~Fireball() {}
    
        virtual void Use() {
          cout << "Boom!" << endl;
        }
    };


    Но теперь мы не скомпилируемся. Компилятор напишет что IMagicSpell::SpellInfo является приватным и недоступен для потомков. Исправить это тоже очень легко.
    Есть три вида секций доступа к полям и функциям класса:
    1. private - приватный доступ, только для экземпляра текущего класса;
    2. protected - ограниченный доступ, только для экземпляров текущего класса и всех публичных наследников; доступ ограничивается при ограниченном или приватном наследовании;
    3. public - публичный доступ, для всех пользователей экземпляров класса.

    Нам надо сменить права доступа к IMagicSpell::SpellInfo с приватного на ограниченный. В этом случае экземпляры Fireball смогут обращаться к IMagicSpell::SpellInfo как к своему ограниченному полю.
    IMagicSpell
    class IMagicSpell {
      protected:
        MagicSpellInfo SpellInfo;
    
      public:
        virtual ~IMagicSpell() {}
        virtual void Use() {}
    
        MagicSpellInfo GetInfo() {
          return this->SpellInfo;
        }
    
        void ShowDebugInfo() {
          cout << "Название: " << SpellInfo.Name << endl;
          cout << "Описание: " << SpellInfo.Description << endl;
          cout << "Тип: " << SpellInfo.Type << endl;
          cout << "Мин. урон: " << SpellInfo.MinDamage << endl;
          cout << "Макс. урон: " << SpellInfo.MaxDamage << endl;
          cout << "Расстояние: " << SpellInfo.Distance << endl;
          cout << "Радиус: " << SpellInfo.Range << endl;
        }
    };


    В таком виде ты сможешь добиться желаемой функциональности. Но куда более важно чтобы тебе было понятно, как именно ты этого добился.
    Ответ написан
    1 комментарий
  • Почему не удается передать строковой литерал в функцию, принимающую char*?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Все дело в том, что строковой литерал в C++ имеет константный тип.
    Когда ты делаешь вызов get_substr("один", "два три четыре"), ты в функцию передаешь два строковых литерала, тип которых - const char[ N ], где N - это длина строки литерала включая терминальный символ '\0'.
    Неявное преобразование типа из const char* в char* недопустимо, поэтому компилятор и пишет тебе ошибку.

    На самом деле, тебе в твоем тексте char* вообще не нужен, т.к. полностью все операции у тебя не приводят к изменению состояния строки. Заменить char* на const char* будет и логичнее, и понимаемость кода тоже улучшит.
    Ответ написан
    Комментировать
  • Будет ли этот код использоваться при компиляции?

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

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

    На самом деле тебе и этот NULL в качестве подстановки не нужен. Зачем тебе в коде программы обилие висящих NULL? Если описать макрос так:
    #ifdef DEBUG
    #define debug(n) Serial.println("***"+String(n)+"***")
    #else
    #define debug(n)
    #endif

    то препроцессор будет просто затирать строку обращения к макросу.
    Ответ написан
    1 комментарий
  • C++. Как записать класс в бинарный файл с помощью перегруженного оператора?

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

    Твой operator<<(ofstream&, const Book&) должен быть определен в пространстве имен std, т.к. ofstreamопределен именно в этом пространстве.

    Правильная перегрузка должна выглядеть так:
    namespace std
    {
        // use the `Book` type definitely from global namespace.
        ofstream& operator<<(ofstream &of, const ::Book &book)
        {
            // ...
        }
    }


    И еще вопрос: что нужно возвращать из этой функции?

    Конкретно этот оператор (в терминологии стандартных стримов) возвращает левый операнд по ссылке.
    Ответ написан
    4 комментария
  • Как ограничить FPS в OpenGL и glut?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Само ограничение частоты кадров делается очень просто. Снаружи idle-функции нужно содержать переменную с временем последнего вызова idle-функции, а в самой функции нужно просто накопить дельту частоты кадров и вызвать glutPostRedisplay.
    double GetCurrentTime()
    {
    	using Duration = std::chrono::duration<double>;
    	return std::chrono::duration_cast<Duration>( 
    		std::chrono::high_resolution_clock::now().time_since_epoch() 
    	).count();
    }
    
    const double frame_delay = 1.0 / 60.0; // 60 FPS
    double last_render = 0;
    void OnIdle()
    {
    	const double current_time = GetCurrentTime();
    	if( ( current_time - last_render ) > frame_delay )
    	{
    		last_render = current_time;
    		glutPostRedisplay();
    	}
    }


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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Стандартом определены правила вывода типа для числового литерала из самого литерала. Также в стандарте определен формат представления числового литерала.
    Согласно этим правилам и этому формату, 5 будет являться числовым литералом с типом int.
    Если бы ты написал 5u, то тип бы уже был unsigned int. А если бы ты написал 5lu, то тип бы уже был unsigned long int.
    Ответ написан
    Комментировать
  • OpenGL не сразу отображает данные из кадрового буфера?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Я не вижу в твоем коде вызова glutIdleFunc. В документации написано что GLUT будет постоянно вызывать переданную функцию в свободное от обработки системных сообщений время. А если в glutIdleFunc передан nullptr (или idle-функция не была задана), то GLUT будет строго ждать следующего системного сообщения.

    Если я все правильно помню, то передача настоящей функции, в которой будет вызываться функция glutPostRedisplay, в glutIdleFunc приведет и к регулярной перерисовке экрана.
    Как правило, в качестве idle-функции в GLUT передают update-функцию, в которой обновляется состояние объектов перед рендерингом.
    Ответ написан
    5 комментариев
  • Какие есть C++ библиотеки для логирования в формате JSON?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вот интересная статья в качестве источника информации.
    https://habr.com/ru/post/313686/

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

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

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

    Контекст можно расположить, скажем, в глобальном пространстве имен в виде глобальных переменных.
    int N = 0;
    bool GreatherThanN(int x) {
        if (x > N) {
            return true;
        }
        return false;
    }

    Такой подход, мягко говоря, неидеален. Его просто стоит знать, как врага, в лицо. :)

    Другим способом является определение контекста через параметры шаблона функции:
    template< int N >
    bool GreatherThanN(int x) {
        if (x > N) {
            return true;
        }
        return false;
    }

    Инстанцирование функции из такого шаблона пройдет через оптимизации и с высокой вероятностью выродится в константу в месте вызова. Но N в этом случае надо знать на этапе компиляции. Да и в целом, не всё можно определить через параметры шаблона. Параметром шаблона может быть или тип, или константа целочисленного типа или типа сводимого к целочисленному (enum, bool, const char*, известные на этапе компиляции указатели).

    Третьим случаем определения контекста является использование т.н. функторов - функциональных объектов.
    Контекст в этом случае укладывается в объекте, а сама функция заключается в operator().
    struct Comparator final
    {
    	int N;
    	
    	inline const bool operator () ( int x ) const
    	{
    		return x > N;
    	}
    };
    
    // ...
    
    Comparator GreatherThanN{ 10 };
    
    // ...
    
    count_if(begin(v), end(v), GreatherThanN);

    Но с появлением лямбд в C++11 такой подход резко убавил в популярности, т.к. его задачи теперь с большим успехом решают лямбды.
    Ответ написан
    Комментировать
  • Что делает оператор ":" в этом случае в c++?

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

    Еще начиная с C++11 в секции инициализации можно указывать делегирование конструкторов. Выбранный конструктор будет так же выполнен до входа в тело текущего конструктора.
    Ответ написан
    1 комментарий
  • Как инициализировать изменяемый 2D массив char в struct C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вот так сработает.
    Пример
    #include <iostream>
    using namespace std;
    
    const char ssid[] = "TEST";
    const char password[] = "123456";
    
    struct Wifi {
        const char *access[2][2];
    } wifi{
        {
            { ssid, password },
            { "DIR", "654321" }
        }
    };
    
    int main() {
    
        for (int i = 0; i < sizeof(wifi.access) / sizeof(wifi.access[0]); i++) {
            cout << wifi.access[i][0] << ": " << wifi.access[i][1] << '\n';
            // TEST: 123456
            // DIR: 654321
        }
    
        return 0;
    }

    Смотри на агрегатную инициализацию.
    Ответ написан
    2 комментария
  • Зачем нужно выравнивание памяти по слову?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Разобраться с вопросом тебе поможет статья: Расставим точки над структурами C/C++.
    Если точнее, то тебе стоит детально изучить первый же пункт статьи: Выравнивание полей памяти.
    В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые — выравниваются на чётные позиции, 4-байтовые — на позиции кратные четырём и т.д.
    Ответ написан
    1 комментарий