Ответы пользователя по тегу C++
  • Как реализовать побитовый сдвиг чисел, которые записаны как строки ( длинные числа хранятся в строках)?

    @Mercury13
    Программист на «си с крестами» и не только
    Как я понял, вы пишете калькулятор. В десятичной системе, чтобы вычисленное калькулятором совпало с вычисленным на бумажке — на что ещё десятичная система? Есть два варианта.
    1. Преобразовать в двоично-длинный формат (264 не входит в long long), сдвинуть, вернуть.
    2. Получить 232 в десятично-длинном формате, и перемножить.
    3. В зависимости от величины сдвига поступить так или этак: например, сдвиги на 1/2/3 вторым способом, а на 4 и больше — первым.
    Думайте сами, что лучше.
    Ответ написан
  • Как использовать потоки в современном C++ в приложении на основе цикла событий?

    @Mercury13
    Программист на «си с крестами» и не только
    Если вы пишете работу на чистом Си++ — придумайте некий интерфейс (в объектном смысле) на чистом Си++ для связи с интерфейсом (пользовательским, написанным на каком-то Qt): передать состояние, выяснить, хочет ли юзверь чего-то и так далее.

    Я считаю, что лучше использовать механизмы наподобие почтовых ящиков, при этом для передачи информации из потока в интерфейс использовать PostMessage и обёртки над ним вроде Qt QueuedConnection, возможно, с дополнительной структурой данных наподобие atomic<unique_ptr<SomeData>>).

    Если работаете на многопроцессорной системе, потоков (кроме главного) меньше, чем процессоров, задержки интерфейса не важны, но важно вытащить из потока последние крохи скорости — ящик неблокирующей синхронизацией, со стороны интерфейса трясти его таймером.

    А future нужно использовать только при взаимодействии потоков-работников, чтобы один подождал, если ему позарез нужен результат второго.
    Ответ написан
    Комментировать
  • В чём ошибка вычисления бесконечно убывающей прогрессии с точностью до эпсилон?

    @Mercury13
    Программист на «си с крестами» и не только
    У вас две ошибки.
    1. Целочисленное переполнение в знаменателе в prog += (double)1/(i*(i+1));. Вычисляйте знаменатель в double, его длины более чем хватит.
    2. Нельзя брать в счёт аналитический результат, надо высчитывать погрешность по имеющимся данным. Поскольку ещё и ряд сходится ХУдожественно — придётся помучиться, 1/(i(i+1)) < k·eps не катит (катит, если элемент ряда убывает экспоненциально, а у нас всего лишь квадратично).
    Ответ написан
    Комментировать
  • Тонкости Компиляторов. Почему в классах с++ не требуется объявление функции до вызова?

    @Mercury13
    Программист на «си с крестами» и не только
    Подозреваю, что объявление функции после вызова разрешили, потому что оно достаточно легко реализуемо, но даёт изрядную свободу прогеру. Например: несколько открытых конструкторов вызывают private init(), а этот init, как и все скрытые методы, сидит где-то в конце.

    Проходов компиляции может быть много — например, сначала мы говорим, что f(x) — это вызов, а вторым проходом определяем, с какой функцией f имеем дело. К тому же конкретно в Си++ любой код в теле класса — автоматический inline, то есть не создаёт кода, пока кто-то снаружи не вызовет.

    Умножение, приведение типа или ещё какая-то дрянь — это особенность метода разбора. Во многих языках, в том числе в Паскале, используется LL-разбор — сканирование слева направо, опознание слева направо. Ошибки наподобие «ожидалась точка с запятой» — это артефакт примитивных LL-анализаторов. В Си используется LR-разбор — сканирование слева направо, опознание справа налево, а ожидающие опознания лексемы хранятся в стеке.

    И вообще, если пишете собственный крайне специализированный язык, делайте его LL(1), это очень упростит разбор.

    Кстати, в Паскале принят обратный порядок видимости: private → protected → public → published. Последнее — поле доступно в рефлексии времени выполнения.
    Ответ написан
    Комментировать
  • Стоит ли использовать типы данных из cstdint?

    @Mercury13
    Программист на «си с крестами» и не только
    Для олимпиадного программирования.
    Если результат до 109 — то int.
    Если до 1018 — то long long!
    Long’ом лучше не пользоваться.
    Можно также использовать int_fast32_t и int_fast64_t.
    Но всё это ТОЛЬКО В СЛУЧАЕ, ЕСЛИ МЫ НЕ ЗАКЛАДЫВАЕМСЯ НА ПЕРЕПОЛНЕНИЕ.
    Если закладываемся — тогда чёткие int32_t и int64_t, без разговоров.
    Ответ написан
    Комментировать
  • Как понять rvalue ссылки? Когда использовать std::move, а когда std::forward?

    @Mercury13
    Программист на «си с крестами» и не только
    Я их называю «врéменные ссылки» — то есть ссылки на временный безымянный объект, возникший в процессе вычисления значения выражения. Отличается тем, что обычно ссылки на временные объекты принимают заоптимизированные деструктивные функции, которые этот объект готовы выпотрошить в своих целях.

    Функция move предназначена для превращения ссылки (именованной или временной) во временную. Например, чтобы сказать: это именованный объект, но спокойно потрошите его, мне он не нужен.

    У Си++ есть одна фишка:
    void bar(std::string&& x) {}
    
    void foo(std::string&& x) {
      bar(std::move(x));
    }
    
    int main() {
      std::string x = "Test";
      foo (std::move(x));
      return 0;
    }

    То есть снаружи foo требует временную ссылку. Но внутри эта ссылка для ошибкобезопасности обычная именованная! В данном случае, когда функция нешаблонная, мы можем поставить move, но что делать в шаблоне?

    Автоопределение типов тут пасует, потому приходится так.
    template <class T>
    void foo(T&& x) {
      bar(std::forward<T>(x));
    }

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

    Таким образом, move — это превращение ссылки (обычной или временной) во временную. Что-то вроде: «Потрошите этот объект спокойно, он мне не нужен».

    Forward — это прикидка, по какой ссылке (обычной или временной) мы передавали объект в функцию, и передача по цепочке таким же образом. Используется только в шаблонах.

    Вот моё описание всего этого.
    https://mercury13-kiev.livejournal.com/96961.html
    Ответ написан
    Комментировать
  • Как сделать чтобы программа не запускала процес на каждый аргумент, а запускала один процес на все аргументы?

    @Mercury13
    Программист на «си с крестами» и не только
    Под Windows это делается через DDE.
    Штука сильно устаревшая, однако осталась именно на это — поддержку нескольких файлов через файловый менеджер.
    Ответ написан
    2 комментария
  • Работа с рисованием в Qt. Почему не получается рисовать после оператора if?

    @Mercury13
    Программист на «си с крестами» и не только
    Дело тут вот в чём. События рисования могут проскакивать когда угодно по любому чиху, и потому механизмы перерисовки надо делать такие, что когда угодно этот paintEvent() проскочит — изображение в окне будет корректно восстановлено.

    Например:
    enum class Shape { NONE, LINE, ELLIPSE, RECTANGLE };
    
    ... в форме...
    Shape shareToDraw = Shape::NONE;

    …соответственно в коде нажатия кнопок устанавливаем эту shapeToDraw,
    а paintEvent реагирует на неё.

    У вас же, как только paintEvent() повторно проскочит, ни одно событие не будет отмечено, и он ничего не сделает. А ведь он может дважды проскочить, чтобы нарисовать непрямоугольную область. Или если оконный менеджер что-то попросил.
    Ответ написан
  • Возможно ли достичь аргумента с нескольким определением типа?

    @Mercury13
    Программист на «си с крестами» и не только
    Конкретно в вашем случае нет нужды извращаться, перегрузка функций — самое лучшее.

    А так здесь есть два вопроса.
    1. А что напишем в скобках вместо непонятного return?
    2. В какой код оно должно скомпилироваться?

    Пример стандартный шаблонный.
    template <class Device>
    SomeFunction getProcAddress(const Device& device, const std::string& name);
    
    template <>
    inline SomeFunction getProcAddress<VkInstance>(
            const VkInstance& device, const std::string& name)
      { return vkGetInstanceProcAddr(device, name.c_str()); }


    Это стандартное использование шаблонов Си++ с далёкого 1998 года.

    Пример через if constexpr. Си++17, значит.
    template <class Device>
    inline SomeFunction getProcAddress(const Device& device, const std::string& name)
    {
      if constexpr (std::is_same_v<Device, VkInstance>) {
        return vkGetInstanceProcAddr(device, name.c_str());
      } else {
        // false нельзя! — проверка произойдёт в любом случае, будет компилироваться ветка или нет.
        static_assert(std::always_false_v<Device>, "Unknown type");
      }
    }

    Используется в функции std::visit, чтобы исполнить разный код для разных вариантов std::variant и ничего не забыть.

    Что написано в скобках — ну, сами видите. Во что скомпилируется — в две разных функции.

    В некоторых случаях — вероятно, не в вашем — можно ещё поиграться с виртуальным полиморфизмом.
    Ответ написан
  • Почему разрешен upcast в RTTI?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Принцип подстановки Лисков — один из важных принципов организации интерфейса объекта. Смысл: само устройство абстракции должно допускать такое, что у нас сын C, но мы безопасно могли работать с отцовскими типами A и B.
    Пример: Отец — прямоугольник с setWidth/setHeight. Сын — квадрат. Значит, уже при проектировании абстракции надо допустить, что setWidth/setHeight или изменит заодно и высоту, или откажется работать, или…
    2. Принцип достаточности. Зачем работать с типом C, если достаточно с B?

    UPD. А для чего этот dynamic_cast вообще? ООП без него проживёт, и часто dynamic_cast — это расписка в плохой конструкции объектов. Но в первую очередь он нужен на API настолько общие, что не совсем понятно, как сделать просто и эффективно. Или требование бинарной совместимости, что важно для всяких там VCL и Qt.
    Ответ написан
    Комментировать
  • Выдаст ли ошибку при аллоцировании памяти?

    @Mercury13
    Программист на «си с крестами» и не только
    Нормально.
    Аварии в деструкторе и конструкторе — дела сложные, но возможные.
    Но тут ни того, ни другого не будет. До вызова конструктора просто не дойдёт.
    } catch (const std::bad_alloc&) {}
    Ответ написан
    Комментировать
  • Как исправить ошибку в коде из-за сравнения с кириллицей/русским алфавитом?

    @Mercury13
    Программист на «си с крестами» и не только
    Итак, нам нужна обработка русского текста, портабельно и поменьше геморроя. Если константа 'Д' многосимвольная — значит, кодировка исполнения UTF-8 и на string забивай, тяжело будет. Работаем в строчке пошире: wstring, u16string или u32string. Главное, разобраться, как правильно выводить всё это в консоль — например, через wcout. То есть:
    wchar_t a;
    if (a == L'Д') {}


    Иногда можно работать и в UTF-8: std::string s; if (s.starts_with("Д")) {} (Си++20!!) Но инструментарий поуже будет, а под Windows с разнобоем кодировок — не советую.
    Ответ написан
  • Как использовать std::begin и std::end в функциях для массивов?

    @Mercury13
    Программист на «си с крестами» и не только
    Я вообще не в курсе, как взаимодействуют Сишный int[] и Си++-ный &arr.
    Но получается, это под капотом превращается в банальный const int*, для которого нет std::begin/end.

    Я бы предложил ещё два распространённых механизма. Первый желательно делать лёгким инлайном, который под капотом превращается в data/size или std::span (если Си++20).

    template <size_t N>
    void myA(const int(&arra)[N], const int(&arrb)[N])
    {
      auto arra_begin = std::begin(arra);
    
      std::cout << *arra_begin << std::endl; // 1
    }
    
    // Си++20!!!!!!!!!!
    void myB(std::span<int> arra, std::span<int> arrb)
    {
      auto arra_begin = std::begin(arra);
    
      std::cout << *arra_begin << std::endl; // 1
    }
    Ответ написан
    Комментировать
  • Где использовать const?

    @Mercury13
    Программист на «си с крестами» и не только
    Это стандартный студенческий код. Можно маленькую ревизию?
    1. Класс хранит строки в виде char*, ссылаясь на чужую память, и ничего хорошего не сделано с этой памятью. Подобные ссылки на чужую память хороши как оптимизация, когда практически всегда передаём строковые литералы (например, названия тэгов XML). А так стандартный способ хранить строку — std::string. Ну или char[], если не учили этого.
    2. Продолжение 1. С одной стороны, setMarka (например) имеет дело с внешней памятью, которой объект не владеет и которую не надо освобождать. С другой — Input передаёт во владение объекту буфер памяти, который освобождать НАДО.
    3. С возвратом true/false — он хорошо сказал. Сам класс должен решать, корректны ли данные.
    4. Деструктор — если std::string разрешён, лучше возьми его вместо const char* и забей на деструктор. Если запрещён — либо используй char[], деструктор не нужен. Либо сделай свой string, способный только управлять памятью неизменяемой строки, и деструктор нужен только ему. В промышленном программировании деструкторы редки: либо это реально какая-то необычная структура данных, либо просто компилятор заглючил и нужно хоть пустое, но тело в CPP-файле. Либо идиома pimpl (pointer to implementation, указатель на реализацию), призванная снизить количество каскадных include’ов — а значит, укоротить неполную сборку.
    5. char* car_name = "Toyota"; — работает только на очень старых компиляторах. Во всех известных мне строковый литерал имеет тип const char*.
    6. Input() должен быть чем-то внешним по отношению к машине: объект «машина» предназначен для хранения данных, и нечего притягивать к нему консоль, которой в программе может и не быть (например, она GUI).
    Ответ написан
  • Для чего нужна двойная ссылка &&?

    @Mercury13
    Программист на «си с крестами» и не только
    Ссылка на временный объект.

    Прямо (int&& d = 5) используется крайне редко, чтобы продлить время жизни временному объекту.

    Куда чаще оно используется в типах возврата (string&& Container::give()), и означает: это ссылка на объект, который спокойно можно выпотрошить. Или в типах параметров — Container& Container::operator = (string&& x): то есть принимает объект, который больше не нужен.

    Для чего потрошить объекты? Вот представьте себе код.
    std::vector<int> doSmth() {
      std::vector<int> r;
      r.push_back(42);
      return r;
    }
    ...
    std::vector<int> a = doSmth();


    Забьём на необязательные оптимизации — как эта штука будет действовать по семантике Крестов?
    std::vector<int> r; — создадим в стековом фрейме переменную r.
    return r; — vector(const vector&) в какое-то место, предложенное вызывающей функцией.
    std::vector<int> a = doSmth(); — vector(const vector&) из этого места в a.

    Си++17 ТРЕБУЕТ, чтобы подобный возврат гарантированно вызывал ОДИН конструктор копирования (а если конструировать на месте вроде return vector<int>{ 42 }; — то ни одного), но у нас-то Си++03. Не много конструкторов? А ведь зная, что объект больше не нужен, можем просто перенести указатель из одного вектора в другой, точка — даже впрямую, без оптимизаций, никаких перевыделений памяти. Вот этот прямой перенос указателей я и назвал словом «выпотрошить объект».

    Пример 1: container = string("alpha") + "bravo"; Тут в контейнер перемещается временная строка.
    Пример 2: string a = "alpha"; container = a; Не компилируется: строка a не временная.
    Пример 3. container1 = container2.give(); Перенос строки из контейнера в контейнер без выделения памяти.
    Пример 4: string a = "alpha"; container = std::move(a); Компилируется (мы функцией move сказали: с обычным объектом обращаться как с временным), но в результате в строке a может быть что угодно и не стоит закладываться на дальнейшее её значение. Но объект после потрошения в любом случае должен быть корректным, ведь ему ещё вызывать деструктор :)

    Последний вопрос: кто потрошит? Функция, которая принимает временную ссылку как параметр. В данном случае Container::op=. Чем потрошит? — ну, например, кодом myString=std::move(x) или std::swap(myString, x). А они, в свою очередь — внутренняя string::op= и дружественная swap — прямыми играми с указателями.
    Ответ написан
  • Как исправить ошибку при компиляции C++ кода?

    @Mercury13
    Программист на «си с крестами» и не только
    Тут поцапались макрос Verify в dbg.h и функция Verify в cryptlib.h.
    Я бы предпочёл переименовать одно из двух — лучше макрос — а затем разобрать, где должен быть макрос, а где функция.

    Виноват определённо разработчик макроса: он назвал его простым и распространённым словом Verify. Функция-то где-то внутри объекта, а макрос заменяет идентификатор Verify на всякую дрянь независимо от того, где этот идентификатор и в каком пространстве имён. Кроме того, подобные макросы принято называть как-нибудь DBG_VERIFY — если только это не макрос совместимости, подменяющий, например, более позднее nullptr в старое NULL.
    Ответ написан
    23 комментария
  • Как сделать, чтобы шаблонная функция принимала const char*?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    template <class T> struct string_traits;
    
        template <class C, class T, class A>
        struct string_traits<std::basic_string<C,T,A>> {
            using Ch = C;
            using Tr = T;
            using Sv = std::basic_string_view<C,T>;
        };
    
        template <class C, class T>
        struct string_traits<std::basic_string_view<C,T>> {
            using Ch = C;
            using Tr = T;
            using Sv = std::basic_string_view<C,T>;
        };
    
        template <class C>
        struct string_traits<const C*> {
            using Ch = C;
            using Tr = std::char_traits<C>;
            using Sv = std::basic_string_view<C>;
        };
    
        template <class C, size_t N>
        struct string_traits<C[N]> {
            using Ch = C;
            using Sv = std::basic_string_view<C>;
        };
    
        template<class S>
        using s_v_t = typename string_traits<std::remove_cvref_t<S>>::Sv;
    
        namespace detail {
            template <class Sv>
            Sv remainderSv(Sv s, Sv prefix)
            {
                if (s.length() <= prefix.length()
                        || !s.starts_with(prefix))
                    return {};
                return s.substr(prefix.length(), s.length() - prefix.length());
            }
        }
    
        /// @brief remainderSv
        ///    Same for prefix only
        template <class A>
        inline auto remainderSv(const A& s, s_v_t<A> prefix) -> s_v_t<A>
            { return detail::remainderSv<s_v_t<A>>(s, prefix); }
    Ответ написан
    Комментировать
  • При наличии каких членов класса move-конструктор не будет сгенерирован автоматически?

    @Mercury13
    Программист на «си с крестами» и не только
    Implicitly-declared move constructor

    If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
    • there are no user-declared copy constructors;
    • there are no user-declared copy assignment operators;
    • there are no user-declared move assignment operators;
    • there is no user-declared destructor.

    then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&).

    Ну и пользовательский move-конструктор исключает автоматический.
    Так что 2, 3, 4, 5, 6.
    Ответ написан
    Комментировать
  • Как преобразовать std::string в LPCSTR?

    @Mercury13
    Программист на «си с крестами» и не только
    std::string dir = "subdir";
    CreateDirectoryA(dir.c_str(), nullptr);
    Ответ написан
    Комментировать
  • Как создать новый экземпляр класса переменной?

    @Mercury13
    Программист на «си с крестами» и не только
    1. В Си++ нет такого понятия, как виртуальный конструктор. А виртуальный конструктор копирования — я вообще не понимаю…
    2. В makecopy вы допустили ошибку — переменная никуда не девается и тупо занимает память.
    3. Чтобы отдать указатель на переменную и сказать: «сам уничтожай, когда хочешь»,— есть стандартный тип unique_ptr. Но для ковариантности по unique_ptr нужны две функции: одна виртуальная и возвращает простой указатель, другая отдаёт конкретный u_p.

    Для простоты предполагаю, что все классы неабстрактные, и виртуальная vclone имеет права доступа public, а не protected.
    #include <iostream>
    #include <memory>
    
    #define IMPLEMENT_CLONE_1(Class, Keyword) \
        virtual Class* vclone() const Keyword { return new Class(*this); }  \
        auto clone() const { return std::unique_ptr<Class>{vclone()}; }
    
    #define IMPLEMENT_CLONE(Class)  IMPLEMENT_CLONE_1(Class, override)
    
    class BaseClass {
    public:
      IMPLEMENT_CLONE_1(BaseClass, )
      virtual ~BaseClass() = default;
    };
    
    class MyClass : public BaseClass {
    public:
      IMPLEMENT_CLONE(MyClass)
      void print() const { std::cout << "I'm a MyClass!" << std::endl; }
    };
    
    int main()
    {
      auto a = std::make_unique<MyClass>();
      auto b = a->clone();
      b->print();
      return 0;
    }


    Есть ещё вот такая хрѣнь. Я уж не знаю, что лучше: сóрок пя́ток или пятóк сорóк прямое преобразование указателей или препроцессор.

    #include <iostream>
    #include <memory>
    
    class BaseClass {
    public:
        auto clone() const { return std::unique_ptr<BaseClass>{ vclone() }; }
    protected:
        virtual BaseClass* vclone() const { return new BaseClass(*this); }
    };
    
    template <class Super, class This>
    class CloneImpl : public Super
    {
    public:
        auto clone() const { return std::unique_ptr<This>{ static_cast<This*>(vclone()) }; }
    protected:
        CloneImpl* vclone() const override { return new This(*static_cast<const This*>(this)); }
    };
    
    class MyClass : public CloneImpl<BaseClass, MyClass> {
    public:
      void print() const { std::cout << "I'm a MyClass!" << std::endl; }
    };
    
    int main()
    {
        auto a = std::make_unique<MyClass>();
        auto b = a->clone();
        b->print();
        return 0;
    }
    Ответ написан