Ответы пользователя по тегу C++
  • Возможно ли достичь аргумента с нескольким определением типа?

    @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;
    }
    Ответ написан
  • Как правильно выделить память (с проверкой выделения) для массива класса?

    @Mercury13
    Программист на «си с крестами» и не только
    Но обычный new выкидывает std::bad_alloc.
    Так что не совсем понятно, что вы хотите сделать, когда делаете new с флагом «не кидать», а потом при неудавшемся new кидаете аварию.
    Ответ написан
    Комментировать
  • Где взять заголовок graphics.h?

    @Mercury13
    Программист на «си с крестами» и не только
    graphics.h — это, очевидно, хедер Borland C++. Его просто нет в многозадачных ОС, где механизмы рисования на формах другие и вообще нет кроссплатформенного графического фреймворка.

    Если нужна просто графика, лучше осваивать SDL. Если графический интерфейс — Qt.

    Если нужно быстро портануть прогу с Borland C++ на что-то более новое —
    https://home.cs.colorado.edu/~main/cs1300-old/cs13...
    openbgi.sourceforge.net
    https://github.com/genpfault/sdl-bgi
    Ответ написан
  • Как имитировать зажатие кнопки на клавиатуре c++?

    @Mercury13
    Программист на «си с крестами» и не только
    Что вы хотите? Если нужен автоповтор кнопки, вам придётся реализовать его вручную.
    Ответ написан
  • Comparison between pointer and integer. Как решить?

    @Mercury13
    Программист на «си с крестами» и не только
    Несколько вариантов.
    1. pnumber превратить в string
    2. strncmp(s[0].pnumber, std::size(s[0].pnumber), "2") == 0
    3. std::string_view(s[0].pnumber) == "2"
    Прости, глючу, и там. и там нужны двойные кавыки
    Ответ написан
    2 комментария
  • Header-only. Страдает ли размер бинарника?

    @Mercury13
    Программист на «си с крестами» и не только
    Для шаблонов Си++ в линкеры пришлось добавлять специальное исключение — ведь расшаблоненные функции будут повторяться в каждом имеющемся объектном файле.

    Теста ради сделал такой проект

    // MAIN.CPP
    #include <iostream>
    
    #include "tmpl.h"
    
    void doFile1();
    
    int main()
    {
        const char* x = "main";
        outThing(x);
        doFile1();
        return 0;
    }
    
    // FILE1.CPP
    #include "tmpl.h"
    
    void doFile1()
    {
        const char* x = "doFile1";
        outThing(x);
    }
    
    
    // TMPL.H
    #pragma once
    
    #include <iostream>
    
    template <class T>
    void outThing(const T& x)
    {
        std::cout << "The thing is " << x << std::endl;
    }


    Для чего x — при расшаблонивании появилось outThing(char[5]&) и соответстсвенно char[8]&.
    А теперь дамп линкера
    Discarded input sections
    
     .text$_Z8outThingIPKcEvRKT_
                    0x0000000000000000       0x50 debug/main.o
    
    Linker script and memory map
    
    .text$_Z8outThingIPKcEvRKT_
                    0x0000000140002900       0x50 debug/file1.o
                    0x0000000140002900                void outThing<char const*>(char const* const&)
    
    Ну и ещё парочка структур для раскрутки стека и подстановки адресов…


    Так что даже при -O0 оно не будет ничего дублировать. Да и логикой понятно: что дублировать, что не дублировать — один хрен потребуется специальная логика, и разницы мало.
    Ответ написан
    1 комментарий
  • Почему берутся проблемы с совместимостью массивов и контейнеров?

    @Mercury13
    Программист на «си с крестами» и не только
    Учите матчасть!
    Версия для буфера памяти предполагает, что данные хранятся в непрерывном буфере nm×double (то есть длина mn, элемент double).
    Но vector<vector> — он НЕ непрерывный буфер.
    Есть непрерывный буфер n×vector<double> — центральный вектор. И каждая из n строчек по отдельности — непрерывный буфер m×double. Относительно друг друга в памяти они могут располагаться как угодно.

    Как исправить? — проще всего
    double s = 0;
    for (i...) {
      s += Sum(m, &a[i][0]);
    }

    Можно через шаблоны работать, но это уже сложнее и я сам не смогу это с листа написать, что уж говорить про первокура.
    Ответ написан
    Комментировать
  • Что такое указатели в С++?

    @Mercury13
    Программист на «си с крестами» и не только
    Если переменных в памяти потребуется слишком большое количество, которое не сможет вместить в себя сама аппаратная часть, произойдет перегрузка системы или её зависание.

    Либо будет дичайше свопить, либо отказ с нехваткой памяти. А уж как этот отказ устроен — через «падение», зависание, какое-то сообщение… — это может быть по-разному.

    Можете себе представить, если бы небезызвестная Battlefield 3 использовала такой метод работы с данными? В таком случае, самым заядлым геймерам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset после нескольких секунд работы игры.

    Нет. Памяти было бы отведено с запасом уже при запуске, и даже небольшая игра требовала бы многие и многие гигабайты памяти, а Battlefield бы просто не запускался даже на топовых ПК. И часть настроек качества не имела бы смысла — изначально, уже при загрузке EXE’шника, выделено памяти с запасом и никак не ужмёшься. Но нет худа без добра — половины глюков и вылетов не существовало бы. Так что мелкие игры, способные бегать на 99% современных и не очень ПК и потому не имеющие настроек качества, этим механизмом вполне пользуются.

    Разрешите попиариться — пишу программу «Юникодия», энциклопедию символов — таблица на 140 тыс. позиций (количество существующих на данный момент символов) занимает большую часть EXE’шника. А таблица на 1,1 млн (ёмкость Юникода) восьмибайтовых указателей тоже выделена статически, но места в EXE’шнике не занимает и заполняется на старте. Таким образом мы может легко пробежаться по всем символам, и можем по коду символа найти его запись.

    Если не уничтожать неиспользуемые объекты, очень скоро они заполнят весь объем ресурсов ПК.

    Указатели — это не только про уничтожение памяти, но и про выделение.

    Но главное, что вы поняли одно: указатель — это адрес в памяти плюс вкомпилированный в программу тип (на железном уровне типов нет).

    И второе: по указателю мы можем получить доступ к объекту, чьего имени мы не знаем (как у меня в таблице символов) или он безымянный (если он выделен динамически, при работе программы, а не вкомпилирован в неё).
    Ответ написан
  • Как сделать такой финт ушами с double?

    @Mercury13
    Программист на «си с крестами» и не только
    Почему нельзя? Пишу на Си с крестами, но там ничего специфичного нет, кроме парочки перестраховок.
    В Си++20 появился ещё и bit_cast.

    #include <iostream>
    
    union DoubleInt {
        double asDouble;
        uint64_t asInt;
    };
    
    static_assert(sizeof(double) == sizeof(uint64_t), "Strange machine with double != int64");
    
    constexpr int BITS_MANTISSA = 52;
    constexpr int BITS_EXPONENT = 11;
    constexpr int BITS_SIGN = 1;
    
    static_assert(BITS_MANTISSA + BITS_EXPONENT + BITS_SIGN == 64, "Programmer's funkup");
    
    constexpr uint64_t MANTISSA_UNIT = uint64_t(1) << BITS_MANTISSA;
    constexpr uint64_t MANTISSA_MASK = MANTISSA_UNIT - 1;
    
    constexpr int EXPONENT_SHIFT = BITS_MANTISSA;
    constexpr uint64_t EXPONENT_MAX = (uint64_t(1) << BITS_EXPONENT) - 1;
    constexpr uint64_t EXPONENT_ORIGIN = EXPONENT_MAX >> 1;
    constexpr uint64_t EXPONENT_MASK = EXPONENT_MAX << EXPONENT_SHIFT;
    constexpr uint64_t EXPONENT_SHIFTED_ORIGIN = EXPONENT_ORIGIN << EXPONENT_SHIFT;
    
    constexpr int SIGN_SHIFT = BITS_MANTISSA + BITS_EXPONENT;
    constexpr uint64_t SIGN_MASK = uint64_t(1) << SIGN_SHIFT;
    
    int main()
    {
        DoubleInt x { -3.45 };
    
        // Простите уж, без денормализованных чисел
    
        // Оставим знак и мантиссу
        DoubleInt xMantissa = x;
        xMantissa.asInt &= (MANTISSA_MASK | SIGN_MASK);
        // И добавим туда стандартный нулевой порядок
        xMantissa.asInt |= EXPONENT_SHIFTED_ORIGIN;
    
        // Извлечём порядок
        int exponent = ((x.asInt & EXPONENT_MASK) >> EXPONENT_SHIFT) - EXPONENT_ORIGIN;
    
        std::cout << xMantissa.asDouble << "*2^" << exponent << std::endl;
    
        return 0;
    }
    Ответ написан
    1 комментарий