Ответы пользователя по тегу 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(); 
    }
    Ответ написан
  • Как сделать "переопределение" функции в 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 не владеет отображаемой памятью, он только дает к ней доступ.
    Ответ написан
  • Разница между enum ID и var?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    На самом деле твой вопрос звучит так: "Зачем нужно обязательно указывать знак ; после определения составного типа?"

    Попробуй определить enum без завершения знаком ;. При попытке трансляции тебе скажут что дальше знака } требуется указание имени объявляемой переменной. Или еще какие-нибудь другие ошибки. Например, что определение типа не допускается там, где ожидается тип результата функции.

    Дело в том, что все следующие записи по своей сути эквивалентны.
    enum { A, B } variable; - объявление переменной типа анонимного перечисления.
    enum Foo { A, B } variable; - объявление переменной типа именованного перечисления.
    enum Foo { A, B }; Foo variable; - Определение типа перечисления, а затем сразу объявление переменной этого типа.
    enum Foo; Foo variable; - Предварительное объявление типа перечисления, а затем сразу объявление переменной этого типа.
    В конце определения любого составного типа допускается объявление переменной или константы этого типа. При этом тип может быть и анонимным, т.е. без имени. Имя типа нужно для указания при объявлении переменной. Таким образом, определить переменную или константу анонимного типа получится только в месте определения самого типа.
    А если мы хотим определить только тип, мы просто ничего не пишем между } и следующим за ним ;.

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

    В msdn написан общий синтаксис с учетом особенностей расширения C++/CX.
    Там же приведено и описание var:
    var
    (Optional) The name of a variable of the enumeration type.


    А особенностью расширения C++/CX является указание уровня публичности типа:
    access
    The accessibility of the enumeration, which can be public or private.
    Ответ написан
  • Что выбрать для освоения 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++.
    Ответ написан
  • Как лучше реализовать локализацию?

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

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

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

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

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

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

    Инвариант объекта гарантируется публичным интерфейсом типа этого объекта. Стало быть, любой публичный доступ к состоянию объекта не должен нарушать инвариант.
    В C++ Core Guidelines присутствует очень хороший совет на эту тему.

    Из этого всего следует что указывать поля публичными тебе практически никогда не надо. Если инварианта нет и поля незачем защищать от внешнего изменения, то тебе стоит оформлять тип как структуру, публичный доступ в которой является доступом по умолчанию. Класс тебе нужен для того, чтобы явно обозначить инвариант своего типа и этим ограничить набор возможных состояний его экземпляров. Наличие инварианта исключает прямой публичный доступ к состоянию экземпляра, т.е. все поля, которые относятся к состоянию экземпляра, нужно пометить как закрытые (protected или private). А для работы с состоянием экземпляра нужно сформировать публичный интерфейс, обеспечивающий сохранение инварианта. Это решается через публичные аксессоры.
    Ответ написан
  • Как правильно передавать массив как параметр?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе в принципе не стоит использовать приведенную тобой форму записи аргументов.
    Дело в том, что тип твоей void func(int arrParamName[10]) будет void(int*).
    Ты не сможешь руководствоваться указанной размерностью (10), т.к. в твою функцию можно передать и массив длины 5. И даже длины 1. Передавать в твою функцию можно массивы любой длины, а еще - nullptr.

    Согласно стандарту тип массива ссылок является недопустимым, поэтому твоя void func(int& arrParamName[10]) является синтаксически неверной записью.

    Теперь о том, как все таки стоит делать. В стандартной библиотеке для нас определен тип std::array.
    Вот именно им и стоит пользоваться. Его можно передать и по ссылке, и по константной ссылке, и переместить тоже можно. И длина у него есть, и итераторами пользоваться тоже можно. И в алгоритмы он прекрасно передается.
    Ответ написан
  • Как работают binary Assignment Operators?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Подробное описание и примеры в документации языка.
    Операции &=, |= и ^= являются побитовыми, а не логическими. В твоих цитатах приведены ошибки описания.

    Как работают побитовые операции.
    5d788934b7918228570392.jpeg

    andB & andA == 0b1000101011 & 0b0000000011 == 0b11 == 3


    Побитовый сдвиг влево производит сдвиг бинарных разрядов в сторону старшего разряда.
    0b00000011 << 1 == 0b00000110
    0b00000011 << 4 == 0b00110000
    0b11000000 << 2 == 0b0000001100000000 // or 0b00000000 for `uint8_t` type.


    Побитовый сдвиг вправо - соответственно, производит сдвиг в сторону младшего разряда.
    0b00110000 >> 1 == 0b00011000
    0b00110000 >> 4 == 0b00000011
    0b00001100 >> 3 == 0b00000001
    Ответ написан
  • Когда удаляются умные указатели?

    @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" для указания пути, куда, собственно, и стоит сохранять загруженные отладочные символы.
    dbg-options-symbols.png?view=vs-2019
    Ответ написан
  • Почему не работает сдвиг влево на переменное кол-во байт?

    @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).
    Ответ написан
  • Как получить значение поля дочернего класса родительским методом?

    @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;
        }
    };


    В таком виде ты сможешь добиться желаемой функциональности. Но куда более важно чтобы тебе было понятно, как именно ты этого добился.
    Ответ написан
  • Почему не удается передать строковой литерал в функцию, принимающую 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

    то препроцессор будет просто затирать строку обращения к макросу.
    Ответ написан