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

    Nipheris
    @Nipheris Куратор тега C++
    однако неопределенные поведения мы либо обрабатывали исключениями, либо как-то логично обрабатывали входные параметры операндов (obj1 + obj2). А как же тут обрабатывается?

    А вот не поверите, есть два вида программистов: те, которые считают, что хорошо замалчивать ошибки, и те, кто УЖЕ так больше не считает. Есть даже статьи на Хабре, которые рассказывают, какой JS хороший и "надежный". Считать замалчивание ошибок надежностью - имхо это значит не стать (пока что) программистом. От ошибок можно уметь восстанавливаться, но замалчивать их нельзя.
    В качестве примера посмотрите на архитектурные подходы в Эрланге. Прям погуглите по словам "erlang let it crash". Этот подход, поддерживаемый концепцией процесса в Эрланге, говорит, что тот код, который не может дальше нормально выполняться, должен "упасть". А мы напишем другой код, который будет следить за работой основного, и в случае нештатных ситуаций будет предпринимать необходимые действия (например, перезапускать процесс).
    Да хотя что далеко ходить, концепция исключений в более популярных языках также есть способ научиться жить с ошибками, не бояться их генерировать, и уметь их обрабатывать там, где это возможно и удобно. Это первое.

    Второе: JS это язык со слабой типизацией. Это такая штука, которая позволяет написать 1 + "1" и безо всяких перегрузок в стиле C++ транслятор сам приведёт какой-то из аргументов к такому типу, чтобы операцию можно было выполнить. Вы сейчас просто в большом удивлении от того, что это возможно и кому-то такое могло прийти в голову (вы из плюсов пришли, да?). Кстати, в PHP почти то же самое. Только правила преобразований другие немного. (Кстати, почитайте об операторах == и === в JS, узнаете много удивительного, если еще не сталкивались).

    Ведь браузерные движки написаны на С++, а значит все это JS-дело определено на уровне С++, мне интересно какие внутренности определяют такое поведение.

    Никакие внутренности не определяют это поведение. Все эти случаи либо явно описаны в стандарте JS и их просто реализуют в соответствии со стандартом, либо реализуют так, чтобы реализации были максимально совместимыми. Тут можете почитать про typeof null, возвращающий "object" - по сути один большой баг, который теперь поддерживают, чтобы не сломать совместимость.

    Из-за этих мелочей в JS минимум системности и логичности. Этим языком правит совместимость, а не логика. Почему он такой, какой есть, вам уже ответили.
    Ответ написан
    9 комментариев
  • Передача функции - шаблоны или std::function?

    Nipheris
    @Nipheris Куратор тега C++
    template<typename Func> void MyFunc (const Func & otherFunc);

    void MyFunc (const std::function<void()> & otherFunc);

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

    С другой стороны, почему-то никто не упомянул такую важную разницу:
    class overloaded_functor {
    public:
    	bool operator()(int a) const { return a > 10; }
    	bool operator()(double a) const { return a > 10.0; }
    	bool operator()(const std::string& a) const { return a.size() > 10; }
    };
    
    template <typename Func>
    void foo(Func f) {
    	// Все варианты компилятся и работают (т.к. сама шаблонная функция foo и все применения оператора вызова к f компилятся ПОСЛЕ того как мы написали foo(overloaded_functor()) );
    	f(3);
    	f(5.4);
    	f("Blah");
    }
    
    void bar(std::function<bool(int)> f) {
    	f(3);
    	// Компилится, но, как и ожидаемо, конвертит double к int, чего мы в нашем случае не хотим
    	f(5.4);
    	// Не компилится вовсе
    	f("Blah");
    }
    ...
    foo(overloaded_functor());
    bar(overloaded_functor());


    std::function по-определению требует указания конкретного типа функции/функтора, которую он оборачивает, поэтому std::function не может работать с перегруженным функтором. Это не так часто нужно, но бывает, например visitor часто реализуется именно таким функтором с несколькими вариантами оператора вызова.

    На всякий случай отмечу, что я ни в коем случае не говорю, что std::function недостаточно мощный вариант. Наоборот, его нужно использовать в большинстве случаев, т.к. в большинстве случаев нужно функция конкретного типа (конкретной сигнатуры и конкретного возвращаемого значения). Однако разницу следует понимать, т.к. эти варианты используются в разных ситуациях.
    Ну и понятное дело, обычный указатель на функцию неудобен, т.к. не позволяет захватить контекст. В Сях для этого существует паттерн userData, и всякий разработчик библиотеки, которая использует callback-и, реализует этот паттерн. В Плюсах для этого есть std::function.
    Ответ написан
    Комментировать
  • Когда вызывается malloc() или new, то на самом деле вызывается какая-то win api функция?

    Nipheris
    @Nipheris Куратор тега C++
    Когда вызывается malloc() или new, то на самом деле вызывается какая-то win api функция?

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

    И при удалении тоже?

    См. выше.

    И вот интересно, а как сделать маленькую кучу, чтобы память выделялась в пределах этой маленькой кучи только?

    Выделяете блок памяти любым из известных вам способов, декларируете функции а-ля myalloc и myfree, и реализуете один из алгоритмов управления свободным пространством в куче (например, двоичного разбиения).

    В C++ можно переопределить операторы new и delete.
    Ответ написан
    8 комментариев
  • Как компилировать butteraugli под Windows?

    Nipheris
    @Nipheris Куратор тега C++
    При этом libpng установлен. Не понимаю, что я делаю не правильно?

    Я думаю, include-директория libpng не была передана компилятору, и нужно это сделать.

    При этом libpng установлен.

    Что вы понимаете под установкой libpng в контексте Windows?)
    Ответ написан
  • Как загрузить готовую 3D модель(из .obj) в программу, использующую DirectX (С++)?

    Nipheris
    @Nipheris Куратор тега C++
    Посмотрите https://github.com/syoyo/tinyobjloader - на беглый взгляд вроде работоспособная либа.
    Ответ написан
    Комментировать
  • Модульность в C++ как это может выглядеть?

    Nipheris
    @Nipheris Куратор тега C++
    Пакеты Джавы и сборки Дотнета - это про модули в терминах крупной структуры приложения, они тут не при чем.
    Речь идет о модулях на уровне программного кода.
    Хотите примерно понять как это будет выглядеть - посмотрите, например, на юниты в Паскале. Это примерно и есть то, что хотят получить в конечном счёте - возможность по-человечески, на уровне языка, объявлять интерфейсы модулей, подключать их друг к другу, следить за зависимостями между модулями (разве что модули C++ вроде как не привязываются к единице компиляции, в отличие от юнитов Паскаля).
    Сейчас в C++ это достигается большим хаком на уровне препроцессора (а не компилятора языка), называющимся include-файлы. Этот механизм, основанный на правиле "много объявлений, одно определение", унаследован из Си, и по сути представляет собой замену отсутствующей возможности передавать метаданные от одной единицы компиляции к другой при сборке проекта. С помощью инклудов вы напихиваете в один файл ВСЕ, что может потребоваться компилятору. По факту выливается в то, что бОльшая часть единицы компиляции после обработки препроцессором - это содержимое инклуд-файлов. И всё это обрабатывается компилятором каждый раз заново (в каждой единице компиляции). В Си это еще куда ни шло, но в плюсах, где многие библиотеки содержат тяжелый шаблонный код, это становится совсем печальным. Помимо этого, сама работа с инклудами - это непростая практика, и если следовать ей неверно, особенно новичкам, они могут получить от компилятора удивительнейшие ошибки. За примером далеко ходить не надо - попробуйте в большом проекте не ставить защиту от множественного инклуда.
    Вся эта препроцессорная магия красоты и прозрачности языку не добавляет. Пока в других языках пишут "package foobar" и "import foobar", мы пишем "#pragma once" или даже "#ifndef FOOBAR_H ...". Уже грустно как-то становится.

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

    Nipheris
    @Nipheris Куратор тега C++
    Для вещественных чисел нет сравнения ==. Есть сравнения (a - b) <= eps. В большинстве случаев так и нужно, т.к. вещественными числами моделируется континуальные величины из реального мира (т.е. те, в которых не обязательно считать с точностью до каждой цифры).

    С другой стороны, для денежных расчётов вещественную арифметику использовать нельзя, т.к. деньги в принципе дискретны. Никто не хочет по определению увидеть 4.74999999...$ вместо 4.75$. Для таких расчетов используются decimal-типы (таких типов не в стандартном C++, есть реализованные в виде библиотек).

    Она должна превращать такие числа 0.475 в такие 475

    Строго говоря, задача поставлена некорректно. Ну или бессмысленно. Такую задачу можно было бы поставить для decimal-типов, которые считаются всегда точно, и количество десятичных цифр в их записи ПРЕДСКАЗУЕМО. А для float у вас будет получаться что-то вроде 4749999999998.

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

    В общем, добро пожаловать в мир машинной арифметики.

    https://en.wikipedia.org/wiki/Floating_point
    https://en.wikipedia.org/wiki/Decimal_data_type
    Ответ написан
    Комментировать
  • Есть ли генератор SQL кода на C++ сразу под несколько БД (MySQL, SQLite, MS SQL)?

    Nipheris
    @Nipheris Куратор тега C++
    Библиотеку для абстрагирования работы с разными СУБД могу посоветовать такую: https://github.com/SOCI/soci , только это не ORM, а именно абстракция от конкретной СУБД и конкретной клиентской библиотеки. Вообще под плюсами ORM не особо живут и плодятся ввиду принципиального отсутствия рефлексии в языке. Какие-то были на базе Qt (MOC как раз и обеспечивает для них наличие мета-объектов), но они слабо развиваются.

    Советую еще раз поразмыслить над необходимостью именно генерировать SQL-запросы - возможно вам стоит писать их самому (тем более раз у вас действия простые), а вот абстракция от СУБД как раз бы пригодилась вам.
    Я конечно не знаю, что у вас за проект, но раз вы используете плюсы, то у вас либо какой-то сервис с низким содержанием бизнес-логики (например, принимающий данные от устройств и пишущий базу), либо иное обслуживающее ПО, в которых обычно не очень много SQL-запросов.
    Ответ написан
  • Как сбилдить релиз версию приложения на Qt?

    Nipheris
    @Nipheris Куратор тега C++
    Неужели нет какого нибудь более простого способа релиз билда приложения на qt???

    Если там правильные DLL (как сказал Ринат Велиахмедов , без суффикса "d") - то вам их надо собрать в одной папке вместе с exe (плюс отдельный разговор о плагинах для платформы). В документации Qt точно был отличный раздел про сборку релиза.. О, вот: https://wiki.qt.io/Deploy_an_Application_on_Windows и doc.qt.io/qt-5/windows-deployment.html
    Ответ написан
    Комментировать
  • Можно ли возвращать лямбды?

    Nipheris
    @Nipheris Куратор тега C++
    Добавлю пару примеров к сказанному MiiNiPaa:
    Вот такой код:
    auto GetLambda() {
        return []( const int &x ) { return x*x ; } ;
    }

    это синтаксический сахар для такого (все это можно было писать еще и до появления лямбд):
    struct Lambda {
    	int operator()(const int &x) { return x * x; }
    };
    Lambda GetLambda() {
    	return Lambda();
    }

    Отличие в том, что явно класс не создается, но принцип тот же. Возможно, на этом примере вам непонятно, зачем вся эта заморочка с классом. Поэтому рассмотрим другой пример. Этот код
    auto GetLambda(int y) {
    	return [y](const int &x) { return x*y; };
    }

    можно переписать так:
    struct Lambda {
    	int y;
    	Lambda(int y) : y(y) { }
    	int operator()(const int &x) { return x*y; }
    };
    Lambda GetLambda(int y) {
    	return Lambda(y);
    }

    Как видите, захват значения локальной переменной и "сопровождение" им кода лямбды осуществляется с помощью оборачивания в класс. Если еще не знакомились с замыканиями, самое время это сделать.
    Ответ написан
    Комментировать
  • Как нормально изменить кодировку в Visual Studio?

    Nipheris
    @Nipheris Куратор тега C++
    Рабочий вариант для работы с UTF-8 строками (2015-я студия). Не забудьте пересохранить исходник в UTF-8 кодировке.
    #include <iostream>
    #include <windows.h>
    
    int main()
    {
    	SetConsoleOutputCP(CP_UTF8);
    	auto message = u8"Тест тест";
    	wprintf(L"%S", message);
        return 0;
    }


    На будущее:
    1) с юникодом и UTF-8 в частности в Винде есть некоторый гемор по ряду исторических причин (в частности, из-за того что родная юникодная кодировка WinAPI - UTF-16); нужно просто уметь решать эту проблему (если нет желания заниматься разработкой на Linux);
    2) это не отменяется того факта, что нужно хорошо знать, что вы вообще делаете. VS - инструмент для работы, особенно это касается C++ проектов, и нужно разобраться с определенными вещами, чтобы им пользоваться. Это я вообще, чтобы вы подход свой поменяли.
    Ответ написан
    Комментировать
  • Чем rvalue-ссылка отличается от lvalue-ссылки?

    Nipheris
    @Nipheris Куратор тега C++
    то в чем профит использования rvalue ссылок в семантике перемещения

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

    Использование std::move - это возможность назвать некоторый постоянный с точки зрения языка объект (например, обыкновенную локальную переменную) временным, когда есть уверенность, что его содержимое не нужно далее по коду. Для него все также будет вызван деструктор (т.е. объект, попавший под move-семантику НЕ считается разрушенным после этого), но после процедуры перемещения считается, что объект находится в некотором неопределенном ("пустом"), но валидном состоянии.
    Ответ написан
    1 комментарий
  • Приведение обьекта производного класса к базовому?

    Nipheris
    @Nipheris Куратор тега C++
    поля объявленные в производном классе исчезают?

    Это называется object slicing. Это явление проявляется в языках, где сложные типы данных вроде классов и записей а) могут наследоваться и добавлять новые поля при наследовании; б) могут присваиваться по-значению (by value). Это и приводит к тому, что поля потомка при присвоении в переменную типа предка отсекаются. Такое присвоение само по себе некорректно, т.к. если вы работаете с объектами классов-наследников через интерфейс базового класса, это подразумевает полиморфное поведение и работу через ссылку/указатель, т.е. чтобы вместо самого объекта копировалась ссылка/указатель на него (т.е. его identity, "уникальный ключ"), а сам оставался нетронутым и лежал там же, где и лежал.

    При работе через ССЫЛКУ на базовый класс (т.е. Base&) таких проблем не будет, однако ссылка сама по себе переменной не является (в отличие от указателя) - это лишь дополнительное ИМЯ для некоторой другой переменной или области памяти. Поэтому, вы не сможете сохранить ссылку в векторе - ваш код не скомпилируется. В векторе вам нужно будет хранить указатели. Например, std::vector<Base*>.

    Запомните простое правило - если ваши объекты подразумевают работу через интерфейс базового класса (т.е. полимофрное поведение), то в большинстве случаев вам следует работать с ними через указатель (и, в большинстве случаев, размещать эти объекты в куче). В том числе через умные указатели (unique_ptr, shared_ptr).

    В других языках (C#, D вроде тоже) существует фундаментальное разделение на типы-значения и ссылочные типы, и для всех ссылочных типов работа "через ссылку" предоставляется автоматически. В C++ такого разделения нет, и как работать с типом вы выбираете сами, при его ИСПОЛЬЗОВАНИИ (т.е. используете либо по-значению, либо через указатель).

    P.S. С днем рождения!
    Ответ написан
    1 комментарий
  • Где следует использовать умные указатели?

    Nipheris
    @Nipheris Куратор тега C++
    Когда их стоит использовать

    Умные указатели? - задан сегодня утром.

    Нужно ли, например, объявлять массив при помощи std::unique_ptr и т.д.

    В большинстве случаев лучше std::array для массивов размера, известного во время компиляции, и std::vector - если размер становится известен только при выполнении программы. Исключение - необходимость взаимодействия с кодом, использующим обычные указатели на массивы (в частности, с Сишным кодом).

    и какой из них?

    std::unique_ptr подразумевает одного владельца в один момент времени, и переход владения от одного умного указателя к другому (ownership transfer). std::shared_ptr использует подсчет ссылок, чтобы у объекта было несколько владельцев. Объект будет уничтожен, когда последний shared_ptr, указывающий на этот объект, будет уничтожен.

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

    Отдельно добавлю, что unique_ptr это один из способов моделирования композиции объектов (когда умирает владеющий объект, умирают и те, которыми он владеет), а shared_ptr - агрегации (если владеющий объект умирают, те, которыми он владел, продолжают жить, пока они нужны кому-то еще).
    Ответ написан
    3 комментария
  • Умные указатели?

    Nipheris
    @Nipheris Куратор тега C++
    Вы, возможно, еще недостаточно освоили ОО-возможности языка C++. Тогда ваш вопрос более чем логичен.

    Видите ли, в языке, где есть какая-либо ОО-модель, неизбежно будут выделяться две категории типов данных - типы данных, экзмепляры которых ведут себя как значения (values), и типы данных, экземпляры которых ведут себя как переменные/объекты (если тут непонятно, опишу подробнее).

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

    Хотя язык C++ позволяет в теории работать с любым типом как "по значению", так и посредством указателей, реальные классы обычно проектируются так, чтобы подчеркнуть их семантику значение/объект. Например, для типов которые ведут себя как "значения" пишется конструктор копирования и разрешаются операции присвоения. Для типов-"объектов" наоборот, операция присвоения и конструктор копирования запрещаются. Конечно, ничто не мешает превратить указатель в ссылку и работать по ссылке, однако использование ссылки "прячет" от нас информацию об identity объекта. Например, для сравнения одинаковости двух объектов (если это именно "объекты", а не "значения") достаточно сравнить лишь их identity, в терминах C++ - указатели. Если вы превратили их в ссылки, вам придется использовать операцию взятия адреса: &a == &b, что менее логично и удобно, чем сравнить обычные указатели: a == b. Кроме того, важным отличием указателя является то, что одним из его допустимых значений является nullptr, что также может быть удобным (а может и нет).

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

    Nipheris
    @Nipheris Куратор тега C++
    К сожалению, все несколько сложнее, и вам нужно лучше разобраться, что происходит в обоих случаях.

    В C++ жизненный цикл объекта, место и способ его существования определяется кодом, создающим объект. Иными словами, тот код, который ИСПОЛЬЗУЕТ класс, определяет, автоматическая ли будет создана переменная (Example a(..)), или динамическая (new Example(..)). Да, в коде класса можно ограничить использующий код, запретив использование конструктора копирования и оператора присваивания, но изначально дизайн языка подразумевает принятие решения не "создателем" класса/структуры, а её "пользователем".

    В C# поведение переменной типа класса/структуры зависит прежде всего от самого класса/структуры. Экземпляры классов, т.е. reference-типов, создаются с динамическим жизненным циклом, и для них работает механизм подсчета ссылок и сборка мусора. Экземпляры структур создаются с автоматическим жизненным циклом, переменные структур хранят значения структур, а не ссылки на них.

    Иными словами, выражение new T(...) - это просто унифицированный синтаксис создания экземпляра типа, независимый от природы этого типа. Удобно это или не очень - отдельный разговор, однако в текущем дизайне языка это достаточно логично (особенно в контексте generic-ов). Аналогично, T a - унифицированный синтаксис объявления переменной типа.

    Нельзя слепо сравнивать то, что происходит в C# и в C++.
    Ответ написан
    1 комментарий
  • Возможно ли как-то БД 1С интегрировать в Qt?

    Nipheris
    @Nipheris Куратор тега C++
    Если 1C хранит данные в SQL, то в общем-то проблем нет, надо только выяснить какая СУБД используется.
    Если 1C хранит данные в sdf-ках, то в принципе тоже можно, но я бы не стал открыть этот ящик Пандоры.
    Ответ написан
    2 комментария
  • Как создать копию объекта по указателю?

    Nipheris
    @Nipheris Куратор тега C++
    Для получения копии объекта его же средствами, необходимо вызвать конструктор копирования, если таковой определен:
    T *src = .... ;
    T *dst = new T(*src);

    Если конструктор копирования не определен, то копировать объект придется вручную.
    Ответ написан
  • Нужен ли сейчас Си?

    Nipheris
    @Nipheris Куратор тега C++
    Си как язык и экосистема, безусловно, нужны много где. Вопрос в том, нужны ли ВЫ этой экосистеме. Любой специались ценен не только знанием языка, но еще и знанием экосистемы и, самое главное, умением решать задачи, где вышеуказанный язык даёт наибольшую эффектиновсть с точки зрения бизнеса.
    Мало кому сегодня нужен человек, знающий PHP, но не знающий ничего о веб-разработке. С Си (да и с C++) то же самое. Чтобы вам давали те задачи, для решения которых имеет смысл использовать именно Си, вам нужно расти как специалист и в других областях. Например, хорошо разбираться в структурах данных. В управлении памятью. В машинной арифметике.
    Сравните например, человека, который просто знает, что в языке Си есть тип float, и человека, которому известно, что в Сишный float не запишешь значение 0.13, не потеряв в точности. А также человека, который понимает, почему точность теряется. И еще человека, который знает, что можно предпринять, если все-таки нужно как-то оперировать с десятичными дробями без потери точности.

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