• Как вычислить Z для линии?

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

    При этом, на самом деле, ни кто не требует чтобы какое-либо из направлений (главное или вторичное) было представлено лишь одной осью. Скажем, вторичное направление может быть представлено плоскостью, вдоль которой приращение будет меньше чем по оси главного направления.

    Допустим, у нас есть тип 3D-позиции.
    struct Vector3i final
    {
    	int32_t x;
    	int32_t y;
    	int32_t z;
    };


    Сам алгоритм Брезенхама стоит выделить в отдельную сущность. Такую сущность можно назвать итератором линии - LineIterator. Такое имя будет хорошо отражать функциональность.
    Будем считать так, что для работы алгоритма мы выбираем главную ось 3D-пространства и вторичную плоскость того же пространства. Такой выбор позволяет лишь немного изменить базовый алгоритм для 2D-пространства.
    Суть алгоритма Брезенхама проста, но сильно ветвится из-за изначальной неясности относительно выбора главного и вторичного направлений. Поэтому, чтобы не плодить код, нам будет лучше ввести специальный прокси для доступа не к конкретным полям объекта позиции, а к его главной и вторичным осям.

    Пример типа AxisProxy
    using AxisPointer = int32_t Vector3i::*;
    
    class AxisProxy final
    {
    public:
        AxisProxy( AxisPointer major_axis, AxisPointer middle_axis, AxisPointer minor_axis)
            : m_major_axis{ major_axis }
            , m_middle_axis{ middle_axis }
            , m_minor_axis{ minor_axis }
        {}
    
    public:
        inline int32_t& AccessMajorAxis( Vector3i& value ) const               { return value.*m_major_axis; };
        inline int32_t& AccessMiddleAxis( Vector3i& value ) const              { return value.*m_middle_axis; };
        inline int32_t& AccessMinorAxis( Vector3i& value ) const               { return value.*m_minor_axis; };
    
        inline const int32_t& AccessMajorAxis( const Vector3i& value ) const   { return value.*m_major_axis; };
        inline const int32_t& AccessMiddleAxis( const Vector3i& value ) const  { return value.*m_middle_axis; };
        inline const int32_t& AccessMinorAxis( const Vector3i& value ) const   { return value.*m_minor_axis; };
    
    private:
        AxisPointer    m_major_axis    = &Vector3i::x;
        AxisPointer    m_middle_axis   = &Vector3i::y;
        AxisPointer    m_minor_axis    = &Vector3i::z;
    };


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

    Примерное объявление типа LineIterator
    class LineIterator final
    {
    public:
        LineIterator( const Vector3i from, const Vector3i to );
    
    public:
        const Vector3i& operator ++ ();
        const Vector3i operator ++ ( int );
    
        inline const Vector3i& operator * () const     { return m_current_point; };
        inline const Vector3i* operator -> () const    { return &m_current_point; };
    
    private:
        static inline const int32_t GetCorrectionStepAxis( const int32_t value )   { return std::abs( value ) << 1; };
        static inline const int32_t GetShiftStepAxis( const int32_t value )        { return ( value > 0 ) - ( value < 0 ); };
    
        void PerformLineStep();
    
    private:
        Vector3i   m_current_point;            // Current position at line.
        Vector3i   m_correction_step;            // Values to change the point corrections.
        Vector3i   m_shift_step;                // The shift step for current point in each iteration.
        int32_t    m_middle_axis_correction;    // The marker for middle axis correction.
        int32_t    m_minor_axis_correction;    // The marker for minor axis correction.
        AxisProxy  m_axis_proxy;                // Point fields proxy.
    };


    Это - обычный поступательный итератор, каждый шаг которого отображает движение вдоль заданной при конструировании линии. Его операторы будут очень простыми.
    Пример кода операторов итератора
    const Vector3i& LineIterator::operator ++ ()
    {
        PerformLineStep();
        return m_current_point;
    }
    
    const Vector3i LineIterator::operator ++ ( int )
    {
        Vector3i current_point{ m_current_point };
        PerformLineStep();
        return current_point;
    }


    С инициализацией будет уже поинтереснее.
    Пример кода конструктора итератора
    LineIterator::LineIterator( const Vector3i from, const Vector3i to )
        : m_current_point{ from }
    {
        const Vector3i line_delta{ to - from };
    
        m_correction_step  = { GetCorrectionStepAxis( line_delta.x ), GetCorrectionStepAxis( line_delta.y ), GetCorrectionStepAxis( line_delta.z ) };
        m_shift_step       = { GetShiftStepAxis( line_delta.x ), GetShiftStepAxis( line_delta.y ), GetShiftStepAxis( line_delta.z ) };
    
        AxisPointer axis[3] = { &Vector3i::x, &Vector3i::y, &Vector3i::z };
        std::sort(
            std::begin( axis ), std::end( axis ),
            [this]( const AxisPointer left, const AxisPointer right ) -> bool
            {
                return m_correction_step.*left > m_correction_step.*right;
            }
        );
        m_axis_proxy = { axis[0], axis[1], axis[2] };
    
        m_middle_axis_correction   = m_axis_proxy.AccessMiddleAxis( m_correction_step ) - ( m_axis_proxy.AccessMajorAxis( m_correction_step ) >> 1 );
        m_minor_axis_correction    = m_axis_proxy.AccessMinorAxis( m_correction_step ) - ( m_axis_proxy.AccessMajorAxis( m_correction_step ) >> 1 );
    }

    Сперва определяется шаг приращения осей - m_correction_step, на базе которого далее производится сортировка указателей на поля осей вектора. Сортируются указатели по убыванию шага их приращения. Именно таким образом определяется главное и вторичное направления. Порядок указателей осей используется для инициализации экземпляра AxisProxy.
    Инициализация двух значений приращения вторичного направления происходит уже с помощью прокси. Начиная с этого этапа нам совершенно безразлично, какие именно оси выбраны в качестве главного или вторичного направлений.

    Функция выполнения шага вдоль линии будет очень простой за счет работы с прокси-объектом.
    Пример функции шага итератора
    void LineIterator::PerformLineStep()
    {
        if( m_middle_axis_correction > 0 )
        {
            m_middle_axis_correction -= m_axis_proxy.AccessMajorAxis( m_correction_step );
            m_axis_proxy.AccessMiddleAxis( m_current_point ) += m_axis_proxy.AccessMiddleAxis( m_shift_step );
        }
    
        if( m_minor_axis_correction > 0 )
        {
            m_minor_axis_correction -= m_axis_proxy.AccessMajorAxis( m_correction_step );
            m_axis_proxy.AccessMinorAxis( m_current_point ) += m_axis_proxy.AccessMinorAxis( m_shift_step );
        }
    
        m_middle_axis_correction += m_axis_proxy.AccessMiddleAxis( m_correction_step );
        m_minor_axis_correction += m_axis_proxy.AccessMinorAxis( m_correction_step );
        m_axis_proxy.AccessMajorAxis( m_current_point ) += m_axis_proxy.AccessMajorAxis( m_shift_step );
    }


    Если убрать поле AxisProxy::m_middle_axis из кода и удалить весь код, где на него есть ссылки, то весь оставшийся код будет представлять из себя обычный итератор линии для 2D-пространства. В этом случае оси даже сортировать не надо будет, для инициализации прокси можно будет обойтись одним тернарным оператором.
    Ответ написан
    Комментировать
  • Как разрабатывать оконные приложения на С++ в Visual Studio 2019?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Самым простым решением для разработки GUI-приложений в среде MSVS2019 будет использование Qt. Собственно, для этого тебе потребуется Qt. Но разработку вести ты cможешь в VS.

    Вторым решением будет чуть более сложное: использование C++/CX вместе с UWP / Windows Forms.

    Третьим решением, еще более сложным, будет использование уже чистого C++ при поддержке MFC или WTL.

    И самым сложным решением будет прямое использование WinAPI.

    Помимо всего этого еще можно воспользоваться библиотекой wxWidgets и на ее базе разработать GUI для своего приложения.
    Ответ написан
    1 комментарий
  • Диапазон типов данных C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Хочу порекомендовать тебе материал от разработчиков PVS-Studio относительно моделей данных в C++. Там хорошо и коротко объясняется суть твоего вопроса.
    Так же тебе будет полезно изучить документацию C++, где можно изучить подробности.

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

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

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

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

    ViewMatrix - матрица отображения любого внешнего пространства в пространство вида - пространство взора наблюдателя. Наблюдатель (камера) воспринимает объекты только внутри своего собственного локального пространства. Чтобы наблюдатель мог увидеть объект, объект необходимо отобразить из его родного пространства в пространство вида камеры, где он или попадет в область проекции, или не попадет.

    Projectionmatrix - матрица отображения видового пространства в пространство проекции вида. Фактически это отображение выполняет проецирование некоторого участка видового пространства на плоскость. Именно отображенная таким образом геометрия становится видимой при презентации кадра.

    Каждый объект сцены находится и анимируется внутри своего локального пространства. Чтобы объект на сцене встал в положенное ему место, его геометрия отображается из своего локального пространства в глобальное пространство сцены с помощью своей ModelMatrix.
    Чтобы сцена стала видна для камеры, всю ее геометрию требуется отобразить в пространство вида камеры с помощью матрицы ViewMatrix.
    Чтобы часть пространства вида камеры попала в область презентации кадра, всю геометрию видового пространства нужно отобразить в пространство проекции с помощью матрицы Projectionmatrix.
    При этом важно заметить, что матрицы Projectionmatrix и ViewMatrix уникальны для камеры, а вот матрицы ModelMatrix уникальны уже для каждого объекта отдельно.


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

    Учитывая что Projectionmatrix и ViewMatrix в процессе презентации кадра являются неизменными, затраты в 48 операций на каждую позицию выглядят как расточительство. Если до презентации выполнить отображение самого пространства вида в пространство проекции, т.е. перемножить ViewMatrix и Projectionmatrix в правильном порядке, то с помощью результирующей матрицы viewProjectionMatrix число операций на одну позицию можно снизить до 32.
    Если же произвести отображение самого локального пространства модели в пространство презентации через перемножение матриц viewProjectionMatrix и ModelMatrix в правильном порядке, то благодаря полученной таким образом MVPMatrix число операций на одну позицию снизится до изначальных 16.

    Таким образом, матрицы viewProjectionMatrix и MVPMatrix просто позволяют снизить трудоемкость презентации кадра. Однако, во время презентации может потребоваться определение положения геометрии в каком-либо промежуточном пространстве, поэтому вдобавок к MVPMatrix в шейдер принято отдавать и ModelMatrix, и матрицы камеры.
    У каждой из матриц свой смысл. И если MVPMatrix бесспорно нужна всегда, то любую другую матрицу в шейдер добавлять стоит только исходя из осмысленной необходимости. Регистры GPU для шейдера не резиновые и могут очень быстро забиться избыточными данными. К тому же, чем меньше на каждом кадре в шейдер передается данных, тем быстрее выполняется презентация кадра.
    Ответ написан
    Комментировать
  • Как лучше реализовать архитектуру MVC/MVP?

    @MarkusD
    все время мелю чепуху :)
    Первым делом стоит обратиться к описанию[T] MVP от Мартина Фаулера.

    Фаулер сразу оперирует поверх GUI на базе модели форм и элементов, т.е. рассматривает твой конкретный случай.
    Модель форм и элементов оперирует событиями элементов для интерпретации пользовательского ввода. Согласно Фаулеру, обработку этих событий стоит передать презентеру. Презентер в этом случае обрабатывает пользовательский ввод, передает его модели и собирает с модели обновленные данные для передачи в вид. Внутренние данные вида, которые не предназначены для модели, должен обрабатывать тоже презентер, после чего все так же передавать в вид.
    Вид должен только отображать данные модели и отсылать в презентер сигналы пользовательского ввода.

    Что будет если сделать по-другому. Например, как описано у тебя в вопросе. Сейчас у себя ты потерял гибкость и буквально разрушил абстракцию презентера. Если презентер вдруг потребуется сделать композитным (например, выбирать видимые и доступные для ввода элементы вида), ты упрешься в рефакторинг и вида, и презентера. Это отсрочит реализацию композитности логики презентера. Если потребуется держать одновременно несколько видов для одной модели, тебе придется как-то внедрять Change Propagation. В рядовом MVP за это отвечает презентер, а у тебя выйдет что вид должен подписываться на уведомление, что характерно уже не для MVP, а для MVC.

    MVP, как и MVC, является архитектурным шаблоном. Такие шаблоны находятся на самом верхнем уровне пирамиды отношений шаблонов. Это говорит о том, что уже просто реализация MVC/MVP в лоб в коде является нежелательной. MVC/MVP задают для кода UI строгое разделение по функциональности: ввод данных, процессинг и вывод данных. Вот что должно явно присутствовать в твоем коде, вот что стоит реализовать с использованием шаблонов дизайна и идиом разработки. Например, презентер или контроллер может быть сформирован на базе Rx и быть полностью децентрализованным, но при этом качественно выполнять свои функции. А вид и вовсе может быть data-driven объектом, т.е. не иметь даже минимальной личной логики.
    Каждый архитектурный шаблон, помимо легкой поддержки, сформирован из расчета на изначальную простоту, возможность стыковки с другими архитектурными шаблонами и потенциальную расширяемость.
    В результате, заложив изначально слабую реализацию MVP, дальнейшими действиями ты рискуешь размыть границы элементов архитектуры, снизить прозрачность реализации для понимания другими людьми и усложнить поддержку этого кода.
    Ответ написан
    Комментировать
  • Flying Bear Ghost 4S почему нет подачи филамента?

    @MarkusD
    все время мелю чепуху :)
    Дело оказалось в порядке полярностей на шлейфе сервы. Обычно серва мечется вместо плавного вращения тогда, когда одна ее обмотка толкает в противоположную другой обмотке сторону.
    В этом случае стоит проследить чтобы последовательность пинов на выходе совпадала с последовательностью на входе.
    Подключение Nema 17 для примера
    nodemcu-esp8266-12-e-controlling-stepper-motor-over-wifi_orig.png
    Ответ написан
    Комментировать
  • Можно ли использовать sfml без openGL в C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Дело в том, что OpenGL является т.н. GAPI, т.е. интерфейсом прикладного программирования графики.
    На базе такого GAPI строятся движки, фреймворки и простые проекты. SFML, будучи фреймворком, точно так же базируется на OpenGL для работы с графикой.

    В связи с этим, если ты не планируешь работать с графикой, то можешь просто не использовать модули Window и Graphics. Как следствие, у тебя не будет возникать зависимости от OpenGL.
    Но если для тебя важно пользоваться графикой SFML, то в его стандартной поставке без OpenGL твой проект не соберется.
    Ответ написан
  • Что дает паттерн билдер по сравнению с обычными сеттерами?

    @MarkusD
    все время мелю чепуху :)
    Для начала стоит обратиться к какому-нибудь источнику информации об этом шаблоне. Например, к этому.
    Представьте сложный объект, требующий кропотливой пошаговой инициализации множества полей и вложенных объектов. Код инициализации таких объектов обычно спрятан внутри монструозного конструктора с десятком параметров. Либо ещё хуже — распылён по всему клиентскому коду.


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

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

    Давай представим что нам надо выполнить рейс на МИ-8. Но чтобы автоматизированная система современного аэродрома дала нам разрешение на взлет, нам надо передать ей специальный хеш.
    Как это могло бы быть...
    МИ-8 - старая штука, на ее борту механический бортовой компьютер - что-то по аналогии с машиной Ч. Беббиджа, только поменьше. Современные машины формируют этот хеш сами из своей телеметрии процесса запуска и маршрута полета. У нас же с собой только мобильник с ПО для формирования этого хеша вручную и обширная приборная панель МИ-8.
    И вот, мы приступаем к запуску МИ-8: запускаем каждый механизм и вписываем показания приборов в ПО на мобильнике. Когда вертолет запущен и план полета установлен, мы жмем в ПО на мобильнике кнопку генерации хеша и получаем строчку этого хеша.
    В связи с тем, что старой техники со своими условностями море, а формат хеша единый для всех, ПО ручной генерации хеша для каждой модели техники сделали отдельно от ПО передачи хеша в диспетчерскую аэродрома. Да и вообще, ПО генерации хеша для старой техники пишется в КБ производителя этой техники. Поэтому нам выдали строковое представление хеша и теперь его надо скопировать из одного ПО и вставить в нужное поле для отправки в другом ПО...

    Так, стоп. Кажется хеш - это интерфейс, оператор ПО внезапно оказался посредником, а ПО для ручной генерации хеша - это билдер что ли? Все именно так.
    Билдер хорошо знает устройство того типа, который он строит. Но ни о самом этом типе, ни о его устройстве посреднику можно не знать. Посредник может знать только интерфейс (это важно, т.е. даже не сам тип билдера, а только его интерфейс) билдера и интерфейс создаваемого объекта. Посредник имеет право обойтись малыми знаниями, которых достаточно для того чтобы передать артефакт работы билдера потребителю.
    Билдер может строить объект совсем другого типа данных, отдает он всегда некоторый обобщенный интерфейс.
    Вдобавок, создаваемый нашим билдером из примера хеш является DTO - т.е. Data Transfer Object, среди прочих свойств которого можно обозначить иммутабильность.


    Иммутабильный объект можно получить через идиому RAII или фабричного метода. Но что если для создания иммутабильного объекта требуется очень много параметров? Например - 16 параметров. Или 23, как в одном случае. Или - 42 параметра.
    Что если часть этих 42 параметров можно вычислить из остальных, но вот иногда их нужно указывать явно?
    Что если всего параметров 42, но для конструирования требуется использовать лишь произвольное подмножество этих параметров? Припоминаются SQL-запросы, правда ведь?
    RAII в этом случае захлебывается и становится непонятным, а фабричных методов требуется столько, что ими становится тяжело управлять. Идиома фабричного метода в этом случае начинает проявлять свои негативные качества и тормозить разработку.
    А билдер со всеми такими случаями легко справляется. Напомню, что Immutable в принципе невозможно снабдить сеттерами, т.к. это нарушит иммутабильность. А если выйти из ситуации через преобразование одного Immutable в другой, то достичь таким способом получится лишь комбинаторного взрыва иммутабильных типов, похоронив тем самым дальнейшую разработку.

    Но давай представим другой пример. Допустим, система диспетчеризации аэродрома принимает сигнал о готовности взлета со стороны борта. Разрешать взлет или нет? Кажется, сперва надо то-то проверить перед резолюцией.
    Как это могло бы быть...
    Нужно осмотреть журналы движения судов, сверить полетные расписания, сделать сверку документов пилота на допуск к полетам сегодня и еще что-нибудь малозначительное, вроде проверки расписания уборки взлетного полотна, чтобы какой-нибудь уборщик не взлетел вместе с бортом на воздух.
    Все это - запросы к куче разных сервисов. Какой-то сервис может Быть слабонагруженным, а у какого-то могут быть перебои в работе. Система же распределенная и отказоустойчивая, в ней есть дублирующие узлы, на которые стоит отправить повтор запроса в случае отказа по запросу с основного узла.
    Все данные нужно собрать в экземпляре сложной аналитической системы, которая состоит из множества довольно сложных стратегий работы со своими данными. Это все значит, что объект, с которым должна работать система диспетчеризации, будет иметь крайне сложный инвариант своего типа. Такой инвариант невозможно собрать на сеттерах, объект просто не склеится. К тому же, зачем системе диспетчеризации знать все тонкости типа такой сложной аналитической системы, когда ей нужен просто ответ - можно вот этому борту взлетать или нет?
    Тут стоит отметить и то, что решение такой задачи в процедурном стиле приведет к все тому же комбинаторному взрыву и перегрузке по логике, потому что сервисный код запросов будет тесно переплетен с кодом логики принятия решений.
    Но система диспетчеризации может просто взять билдер и сформировать на его базе граф асинхронных задач запросов к внешним системам. Именно в топологии этого графа и отражаются зависимости запросов к резервным системам, а результаты запросов складываются в билдер в произвольном, полностью асинхронном порядке.
    Когда билдер возвращается к системе диспетчеризации из асинхронной среды, система диспетчеризации просто создает из него объект аналитической системы, с которой дальше и ведет все свои беседы по душам.


    Билдер призван решать вопросы перегрузки по знаниям типов, перегрузки по сохранению отказоустойчивости и перегрузки по сложности создания экземпляров. И использовать его стоит именно для решения таких задач.
    Еще билдер позволяет развести логику сеттеров отдельно от логики результирующего типа, это бывает очень полезно.
    И, да, важно отметить еще и то, что если инвариант типа допускает наличие сеттеров, если в том, что пользовательская система знает этот тип в лицо, нет проблемы, если сами сеттеры не перегружают логику типа, то билдер уже явно не нужен.
    Ответ написан
    1 комментарий
  • Возможно ли перенести "внешний" интерфейс одной библиотеки в другую через статическую связку?

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

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

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

    Есть несколько способов обойти правило компоновки библиотек.
    Первый и самый простой: вынудить компоновщик обработать всю библиотеку целиком. Это делается через представление библиотеки как Whole Archive[?].
    Однако, в этом случае вся статическая библиотека будет скомпонована в исполняемый код. Это не всегда бывает удобно. Особенно если в библиотеке находится много отладочного кода.

    Второй вариант - это использовать __attribute__((used))[?] или [[gnu::used]] на новый лад.
    В коде для Visual C++ можно использовать #pragma comment(linker: "/include:")[?].
    Данные конструкции помечают функцию как важную для компоновки, в результате чего функция всегда будет компоноваться из статической библиотеки в бинарный код.

    Вариант третий - это использовать опцию -u[?] компоновщика.
    Для Visual C++ такой опцией будет /INCLUDE[?].
    Указание важных функций для принудительной компоновки прямо в сценарии сборки является наиболее прозрачным и удобным методом с точки зрения дальнейшей поддержки кода.
    Ответ написан
    2 комментария
  • Ошибка при создании явной специализации. В чём ошибка?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    template <typename T>
    T maxn(T arr[], int arrSize);

    Это - общая форма шаблона функции. Если тебе нужно сделать явную специализацию шаблона, твоя явная специализация должна соответствовать этому общему шаблону.

    template <>
    const char* maxn(const char* arr[], int arrSize);

    Сырые однобайтовые строки в C++ (при соблюдении правила нуль-терминации) определяются типом char* или const char* если строку не планируется модифицировать. Наша функция не планирует модифицировать строки, следовательно явная специализация шаблона должна работать с типом const char*.
    Тип const char* должен быть подставлен везде вместо параметра шаблона T.

    Таким образом явная специализация шаблона для сырых строк будет полностью соответствовать общему шаблону функции.

    А теперь немного о твоей ошибке.
    template <> char* maxn(const char *arr[], int arrSize);

    Это объявление не является явной специализацией приведенного выше шаблона. Это - определение перегрузки функции через явную специализацию какого-то иного неописанного шаблона. Это потому что результат функции char* отличен от параметра функции const char*.
    В целом, эта запись является плохо сформулированной (ill-formed) по целому ряду причин, включая и то, что перегрузка не может осуществляться через изменение типа результата функции.

    В целях обучения я советую не опускать перечисление аргументов явной специализации шаблона:
    template <>
    const char* maxn<const char*>(const char* arr[], int arrSize);

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

    @MarkusD
    все время мелю чепуху :)
    Для начала стоит учесть общую систему опознания осей в 3D-пространстве. Оси X, Y, Z помечаются каналами цветов R, G, B в однозначном соответствии. Ось X всегда и везде красная. Ось Y - зеленая. А ось Z - синяя.

    glVertex3f(0, 0,znear);
    glVertex3f(0, 0,zfar);

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

    Сдвинь начальные координаты осей X и Y от нуля (хоть на 1pt), измени цвет оси Z, тогда ее станет немного лучше видно.
    Ответ написан
    Комментировать
  • Как разбить строку по разделителю?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Это простой способ поделить строку на подстроки.
    inline std::vector<std::string_view> SplitString( std::string_view input_string, const char separator )
    {
    	std::vector<std::string_view> parts;
    
    	size_t part_length = 0;
    	while( ( part_length = input_string.find( separator ) ) != input_string.npos )
    	{
    		parts.emplace_back( input_string.data(), part_length );
    		input_string.remove_prefix( part_length + 1 );
    	}
    
    	if( !input_string.empty() )
    	{
    		parts.push_back( std::move( input_string ) );
    	}
    
    	return parts;
    }
    Ответ написан
    2 комментария
  • Какие языки совместимы с C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Судя по комментариям к ответам, у тебя формулировка вопроса не соответствует твоим ожиданиям.
    Совместимость языков - это тонкий маршалинг типов и совместная линковка в один бинарный файл.
    Этим условиям, в разной степени, удовлетворяют: C, asm, D, C#, Java. Может быть еще какие-то языки. При этом, у каждого языка верхнего уровня будут свои требования к маршалингу типов и передаче управления.

    Тебя же интересуют встраиваемые языки.

    Взять, например, D. Он полностью совместим с C++. Однако, его требования не позволяют простую линковку объектных файлов C++ и D в один бинарный файл. На C++ или должен быть создан D Runtime (что не так просто сделать), или C++ код должен линковаться в бинарник на D, в качестве Better C++. Т.е. получается наоборот, в базе будет D, а C++ его только расширяет.
    С питоном и Java все точно так же. C# вообще работает с библиотеками классов, написанными на C++/Cx, т.е. на модифицированном C++.
    Эти языки не являются встраиваемыми. Их возможности расширяются за счет использования C++.

    Хорошим ответом на твой вопрос будет вот такой список встраиваемых языков. Из этого списка можно брать любой язык, ядро которого написано на C или C++.
    Мой личный выбор - это AngelScript и Lua/Terra.
    Ответ написан
    Комментировать
  • Android NDK Error: no matching member function for call to 'push_back'?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    class rck{
    public:
        unsigned short x,y;
        void show();
        int tm=0;
        uint8_t type=0;
    };

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

    Лучше будет с молоду учиться правильно оформлять свой код. Например так.
    struct rck final
    {
    	uint16_t x = 0;
    	uint16_t y = 0;
    	int32_t tm = 0;
    	uint8_t type = 0;
    	
    	
    	void show();
    };

    Не берусь менять имена, т.к. не понимаю их семантики. Каждое имя должно отражать семантику своего существования или должно быть стерто из кода.


    У тебя объявлен, вроде бы, агрегатный тип с неявным конструктором по умолчанию, в котором выполняется инициализация только полей tm и type.

    rock.push_back({(uint16_t)(x*64),(uint16_t)(y*64),0,0});

    Тут перед обращением к push_back формируется фиктивный std::initializer_list<uint16_t> с двумя элементами внутри.
    В общем смысле, до полного списка аргументов нужно указать еще два аргумента. Тогда эту запись можно будет считать агрегатной инициализацией временного объекта типа rck.

    Такая форма агрегатной инициализации стандартизирована в C++11. До C++14 агрегатная инициализация отключается для типов, в которых присутствует инициализация полей по месту объявления (в коде это поля tm и type). Каждый следующий стандарт требования к агрегатной инициализации только ужесточает.

    GCC давно славится своей слабой поддержкой стандарта языка. Это означает что если код проходит трансляцию g++, это далеко не факт что он соответствует стандарту.
    NDK сегодня для сборки использует clang, который на данный момент считается максимально приближенным к стандарту транслятором.

    Все это должно дать понять что данный код не соответствует стандарту языка и не должен компилироваться. Подходящего конструктора от двух аргументов для типа rck не определено и тип не является агрегатным.
    То, что g++ смог собрать этот код, останется на совести g++.
    Еще одним важным моментом будет точное понимание, какой именно стандарт языка выбран для трансляции твоего кода.
    Ответ написан
    1 комментарий
  • Что лучше использовать #include или LoadLibrary?

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

    При неявном связывании компоновщик добавляет в PE-секцию импорта дополнительный блок импорта конкретно указанной библиотеки. Этот блок обрабатывается загрузчиком PE в момент подготовки к запуску процесса. Эта информация тебе уже должна быть знакома. К моменту передачи управления в точку входа процесса все неявно связанные библиотеки уже загружены и готовы к работе. Код процесса не нуждается в явном управлении загрузкой таких библиотек.

    Явное связывание выполняется полностью кодом процесса, для чего и применяется набор функций для работы с библиотеками. Явное связывание - это большая рутина. Но иногда без этой рутины никак.
    Поиск динамических библиотек при работе LoadLibrary[?] осуществляется в соответствии с определенным порядком. При этом, путями для поиска динамических библиотек можно управлять в ручном режиме, добавляя новые или замещая имеющиеся пути поиска.

    Директива препроцессора #include[?] лишь косвенно относится к вопросу. На самом деле к вопросу относится директива #pragma comment( lib )[?].
    Прагма работает только для cl - компилятора от Microsoft. Подключаемая таким образом статическая библиотека должна находиться в перечисленных Library Path, в настройках сборки проекта. Эта прагма является альтернативой прямого указания статической библиотеки в настройках сборки проекта.
    Эту директиву любят использовать в сторонних заголовках, код для которых поставляется только в бинарном виде, в статических или динамических библиотеках. Вместе с динамической библиотекой такого решения поставляется и библиотека статическая, в которой находятся инструкции для неявного связывания с динамической библиотекой. Результатом работы #pragma comment( lib ) с такой статической библиотекой будет неявное связывание твоего кода с указанной динамической библиотекой.

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

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Drottarutarnum, p = { s }; - это Copy Initialization.
    В буквальном смысле, что тут происходит. Сперва ты создаешь temporary local через конструктор Protocol::Protocol(Serial &s). Следом, через operator =, который сводится к конструктору копирования/перемещения, ты делаешь Copy Construction объекта p. Делается это через конструктор копирования/перемещения по умолчанию, который просто копирует/перемещает поля используя их конструкторы копирования/перемещения.

    Лямбда создается внутри temporary local значения, т.к. именно для него вызывается соответствующий конструктор. Далее эта лямбда остается жить в объекте Serial, а сам temporary local уничтожается сразу же после конструирования p.

    После этого в замыкании лямбды остается dangling pointer на уже уничтоженный this.

    Одним из возможных решений данной проблемы может быть не p = { s };, а p{ s }; или, как ты уже описал, p = s;. Такой тип инициализации уже называется Value Initialization. Но по своей сути это только отсрочка проблемы. Тебе придется очень пристально следить за жизнью такого объекта, т.к. вероятность обрести все тот же висячий указатель в замыкании в следствии неявного копирования крайне высока.

    Другим из возможных решений такой проблемы может быть использование std::enable_shared_from_this[?], std::shared_ptr[?] для хранения протокола и std::weak_ptr[?] в замыкании твоей лямбды. Этот метод гарантированно решит твою проблему, но может привести к ряду других проблем с возможной утечкой ресурсов.

    В качестве еще одного способа - научить Serial отцеплять лямбду уничтожаемого Protocol и написать правильный конструктор копирования Protocol, соблюдая правило 3/5/0.

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

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

    [&p, &all_files_and_sums]
    Если с захватом all_files_and_sums по ссылке я не спорю, то точно ли p продолжит существовать после выхода из итерации? Давай подумаем. А после выхода из цикла продолжит?
    У меня вообще нет уверенности в том, что после смещения bfs::directory_iterator адрес возвращаемого им bfs::directory_entry изменится. Я бы предпочел захватывать копию bfs::directory_entry в лямбде.

    У тебя нет ожидания окончания работы от пула потоков - нет синхронизации с завершением запланированных задач. Точнее... ну как нет... У тебя жесткая синхронизация через std::future::get[?]. По факту в этот момент у тебя блокируется главный поток до момента обработки запланированной задачи. У тебя всегда планируется только одна задача. А видимость конкурентной работы создается лишь потому что какой поток ее схватил, тот и работает. Видимо задачи у тебя быстро обрабатываются, раз ты глазами этого не увидел.

    Тебе стоит сохранять сами std::future от задач. В момент планирования их результат еще не определен и get вызывать не надо. Надо дождаться завершения работы всех потоков в пуле и исчерпания всех задач. Для этого у тебя в пуле должны быть продуманы механизмы оповещения.
    После обработки всех задач ты можешь вызывать std::future::get, получать результаты и производить свои операции над ними.
    Альтернативно, ты можешь более тонко реагировать на завершение каждой задачи и появление в ее std::future результата. Это тоже можно сделать. Просто сделать это надо своими руками и продумав масштабируемость такого механизма.

    И в дополнение. Зачем тебе boost? Ты пользуешься std::future и лямбдами, ты пишешь в рамках стандарта C++11. Тебе доступны и std::thread, и все примитивы барьерирования из std.
    В твоем распоряжении вся Thread Support Library. А boost тут явно лишний.
    Если переключишься на C++17, то тебе и boost::filesystem не будет нужна, т.к. станет доступна std::filesystem - Filesystem Library.
    Ответ написан
    Комментировать
  • Как правильно передать в RegSetValueEx в lpData NULL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Начнем с простого.
    sizeof( expresion ) позволяет узнать минимально допустимый размер памяти, требуемый для того чтобы память могла вместить объект с типом результата утверждения expresion.
    sizeof( value ) вернет размер LPCWSTR, т.е. размер типа const wchar_t*.

    wcslen не допускает передачу нуля в качестве своего параметра. Забота об этом лежит полностью на тебе.

    RegSetValueExW может принимать нуль в параметре lpData, но тогда cbData обязан содержать 0. Но в целом, это очень плохая практика. Если тебе нужно записать пустую строку, пиши пустую. строку. Т.е. "", но не нуль.

    cbData должен быть задан размером буфера строки в байтах, включая терминальный символ, или оба терминальных символа в случае многострочного аргумента в lpData. Важным замечанием будет именно то, что в cbData указывается размер в байтах, а не в символах. У тебя в lpData передается строка из wchar_t, размер которого больше одного байта.

    Далее, по самому коду. Его тяжело читать.
    Как читать будет легче
    bool modifyKey( HKEY root_hey, const std::wstring_view& path, const std::wstring_view& key_name, const std::wstring_view& new_value )
    {
       HKEY folder_key;
    
       if( RegCreateKeyW( root_hey, path.data(), &folder_key ) != ERROR_SUCCESS )
       {
          return false;
       }
    
       const BYTE* const value_buffer = reinterpret_cast<const BYTE*>( new_value.data() );
       const DWORD buffer_size = static_cast<DWORD>( new_value.length() * sizeof( wchar_t ) + 1 );
       if( RegSetValueExW( folder_key, key_name.data(), 0, REG_SZ, value_buffer, buffer_size ) != ERROR_SUCCESS )
       {
          RegCloseKey( folder_key );
          return false;
       }
    
       if( RegFlushKey( folder_key ) != ERROR_SUCCESS )
       {
          RegCloseKey( folder_key );
          return false;
       }
    
       RegCloseKey( folder_key );
       return true;
    }
    Ответ написан
    7 комментариев
  • Как создать папку с учетом имени пользователя c++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для современных версий ОС Windows в наборе WinAPI присутствует функция SHGetKnownFolderPath[?].
    Она позволяет получить путь к некоторым предварительно определенным папкам, набор которых описан абстракцией KNOWNFOLDERID[?].

    Тебе подойдет FOLDERID_Desktop для получения папки рабочего стола пользователя.
    При работе с этой функцией очень важно не забывать пользоваться функцией CoTaskMemFree[?] для освобождения памяти, переданной из функции через указатель ppszPath.

    Небольшой пример использования
    wchar_t* path_buffer = nullptr;
    if( FAILED( ::SHGetKnownFolderPath( FOLDERID_Desktop, KF_FLAG_DEFAULT, 0, &path_buffer ) ) )
    {
    	::CoTaskMemFree( path_buffer );
    	LOG_ERROR( LOG_CHANNEL, "Failed to get known directory; error - #{:08X}.", ::GetLastError() );
    	// terminate ...
    }
    
    std::wstring user_desktop_path{ path_buffer };
    ::CoTaskMemFree( path_buffer );
    
    // whatever with `user_desktop_path` ...
    Ответ написан
    Комментировать
  • Android как создать папку средствами NDK?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Через NDK доступна функция mkdir[?].
    Ее поведение для Android ни чем не отличается от изложенного в официальной документации.

    Для проверки существования папки можно использовать функцию stat[?], а можно опереться на код ошибки EEXIST из mkdir.
    Ответ написан
    Комментировать