Ответы пользователя по тегу C++
  • Как нарисовать такую звезду openGL?

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

    Псевдокод:
    void display()
    {
       glClear( GL_COLOR_BUFFER_BIT );
       glBegin( GL_LINE_LOOP );
    
       for( size_t index = 0; index < vertices.size(); ++index )
       {
          const Vertex& vertex = vertices[ ( index * 3 ) % vertices.size() ];
          glVertex2i( vertex.x, vertex.y );
       }
    
       glEnd();
       glFlush(); 
    }
    Ответ написан
    1 комментарий
  • Как сделать "переопределение" функции в c++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Твой вопрос можно решить таким способом.
    #include <iostream>
    #include <functional>
    
    class Enemy final
    {
    public:
    	std::function<void()> update = []() {};
    };
    
    int main( int argc, char* argv[] )
    {
    	Enemy enemy;
    	enemy.update = []() { std::cout << "Hello"; };
    
    	enemy.update();
    	return 0;
    }
    Ответ написан
    Комментировать
  • Как std::initializer_list определяет количество элементов в {списке}?

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

    Foo bar = {...};
    Foo bar{...};


    В обоих случаях выполняется List Initialization, в первом - copy list-initialization, во втором - direct list-initialiaztion.

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

    Образно выражаясь, транслятор прямо перед вызовом конструктора объекта оформляет короткую область видимости, в рамках которой оформляется локальный массив неизменной длины. В этот локальный массив по своему значению складываются аргументы конструктора, далее этот массив обрамляется в std::initializer_list, с которым конструктор и вызывается.
    Сразу по завершении конструктора локальная область видимости закрывается и память массива аргументов конструктора освобождается. Поэтому std::initializer_list нельзя копировать, перемещать, сохранять в состоянии конструируемого объекта. std::initializer_list не владеет отображаемой памятью, он только дает к ней доступ.
    Ответ написан
    Комментировать
  • Что выбрать для освоения DirectX - UWP или Win32 API?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Microsoft UWP является надстройкой для создания универсальных приложений, которые, в теории, должны легко переноситься между различными аппаратными слоями платформы Windows. UWP поддерживается только в Win10, реализована на базе расширения C++/CX стандарта C++17 и выполна преимущественно в объектном стиле.

    Windows API является низкоуровневым слоем взаимодействия между приложением и операционной системой. Win32 API поддерживается всеми версиями Windows, начиная с Windows 95. Однако, разные версии Windows поддерживают разные наборы функций. Поэтому уровень поддержки той или иной функции из набора API всегда следует уточнять в документации. Реализован Win32 API на языке C стандарта C99 и выполнен в процедурном стиле.

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

    C++/CX является расширением стандарта и не поддерживается другими платформами, а так же всеми компиляторами кроме компилятора Microsoft. При этом, существуют такие условия, когда отказаться от использования C++/CX и UWP невозможно. В иных ситуациях следует принимать к сведению особый синтаксис расширения и объектную организацию. Скажем, я бы не стал полностью весь проект делать на базе C++/CX.

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

    В любом случае, выбрав UWP или WinAPI, дальше тебе все равно лучше работать исключительно со стандартным C++ и стандартной реализацией DirctX для C++. От C++/CX стоит избавляться на как можно ранних слоях абстракции палатформозависимого кода. От типов и абстракций WInAPI, равно как и от наследия C99, тоже лучше избавляться как можно раньше и переходить на работу исключительно со стандартным C++.
    Ответ написан
    1 комментарий
  • Как лучше реализовать локализацию?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    s = loc::get("key");    // ambiguous overload for op=

    неоднозначность у тебя появляется потому что оба конструктора std::wstring (конструктор копирования и конструктор преобразования из const wchar_t*) являются неявными.
    Операторы преобразования TaggedCWstr так же являются неявными.

    Эту неоднозначность нужно исключить. Замени, например, оба оператора преобразования на оператор преобразования в std::wstring_view.

    Я бы не стал делать неявный оператор преобразования в std::wstring и этим позволять бесконтрольно обращаться к динамической памяти без явного понимания этого процесса. Лучше для получения std::wstring сделать operator *, а еще лучше - вообще не ломать семантику операторов и сделать метод с говорящим именем.
    Ответ написан
    1 комментарий
  • Как написать свой 3D рэндер?

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

    @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 комментариев