Ответы пользователя по тегу C++
  • Содержит ли объект класса std::string строку с терминирующем нулем (т.е. '\0')?

    @Bladegreat
    До С++11 строки могут не содержать нуль-терминатор, а начиная с C++11 - обязаны. Метод data() возвращает то же самое, что и c_str().

    https://en.cppreference.com/w/cpp/string/basic_str...
    Ответ написан
    Комментировать
  • Как в массиве хранить указатели на объекты разных типов?

    @Bladegreat
    Данную проблему можно решить с помощью паттерна Visitor. Ниже приведен пример реализации данного паттерна. Он громоздкий, но сложного в нем ничего нет.

    В начале нужно обратить внимание на класс MyVariant. Так как C++ имеет строгую типизацию, то мы не можем просто так записать любой объект в любую переменную, как в языках с динамической типизацией, поэтому вначале нам нужно избавится от типа. В языке C для этих целей обычно используется void* и в этом примере мы будем использовать именно его. При получении создании объекта MyVariant будет создан указатель на int или string, а затем тип будет приведен к void* и записан в переменную var_. Но проблема возникнет, когда тип нам все-таки понадобится, поэтому предварительно сохраним информацию о нем в отдельную переменную type_. В общем, в конструкторах ничего интересного нет: мы объявили конструктор для int, string и конструктор перемещения. Удалять void* напрямую нельзя, поэтому в деструкторе мы приводим указатель к нужному типу перед удалением (static_cast).

    В main создается массив объектов типа MyVariant, но работать с ними просто так мы не можем, так как у нас больше нет информации о типах и нам нужно ее предварительно восстановить. Для этого объявлен интерфейс VisitorInterface и его реализация в виде PrinterVisitor, которая выводит значение в терминал. В main создается объект PrinterVisitor и передается в метод Visit объекта MyVariant, который, имея информацию о типа в переменной type_, приводит void* к int* или string*, а потом вызывает соответствующий метод из VisitorInterface.

    #include <string>
    #include <vector>
    #include <iostream>
    
    
    class VisitorInterface
    {
    public:
        virtual void Dispatch(int) = 0;
        virtual void Dispatch(const std::string&) = 0;
    };
    
    class PrinterVisitor : public VisitorInterface
    {
    public:
        void Dispatch(int i) {std::cout << i << std::endl;}
        void Dispatch(const std::string& s) {std::cout << s << std::endl;}
    };
    
    class MyVariant
    {
    private:
        enum class Type
        {
            None,
            Int,
            String
        };
    
        void* var_ = nullptr;
        Type type_ = Type::None; ///< Type of var_ variable
    
    public:
        explicit MyVariant(int val)
        {
            var_ = new int(val);
            type_ = Type::Int;
        }
    
        explicit MyVariant(const std::string& val)
        {
            var_ = new std::string(val);
            type_ = Type::String;
        }
    
        MyVariant(MyVariant&& other)
        {
            var_ = other.var_;
            type_ = other.type_;
            other.var_ = nullptr;
            other.type_ = Type::None;
        }
    
        ~MyVariant()
        {
            switch (type_)
            {
            case Type::Int:
                delete static_cast<int*>(var_);
                break;
            case Type::String:
                delete static_cast<std::string*>(var_);
                break;
            }
        }
    
        void Visit(VisitorInterface& visitor)
        {
            switch (type_)
            {
            case Type::Int:
                visitor.Dispatch(*static_cast<int*>(var_));
                break;
            case Type::String:
                visitor.Dispatch(*static_cast<std::string*>(var_));
                break;
            }
        }
    };
    
    int main(int /*argc*/, char** /*argv*/)
    {
        std::vector<MyVariant> vec;
        vec.emplace_back(5);
        vec.emplace_back("Hello habr!");
        vec.emplace_back(10);
    
        PrinterVisitor printer;
        for (auto& el : vec)
        {
            el.Visit(printer);
        }
    
        return 0;
    }


    Данное решение будет работать, наверное, в любой версии C++, но в C++17 появилась более удобная альтернатива: std::variant + std::visit.

    В данном примере мы создаем массив значений типа std::variant. Для доступа к значению конкретного элемента затем используется std::visit, который автоматически сделает все, что было сделано в первой реализации (сгеренирует перегруженные методы и switch блоки). Для реализации различной логики в зависимости от типа мы используем if времени компиляции.

    #include <string>
    #include <vector>
    #include <iostream>
    #include <variant>
    
    int main(int /*argc*/, char** /*argv*/)
    {
        std::vector<std::variant<int, std::string>> vec;
        vec.emplace_back(5);
        vec.emplace_back("Hello habr!");
        vec.emplace_back(10);
    
        for (auto& el : vec)
        {
            std::visit([](auto& arg){
                using T = std::decay_t<decltype(arg)>;
                if constexpr(std::is_same_v<T, int>)
                {
                    std::cout << "int: " << arg << std::endl;
                }
                else if constexpr(std::is_same_v<T, std::string>)
                {
                    std::cout << "string: " << arg << std::endl;
                }
            }, el);
        }
    
        return 0;
    }
    Ответ написан
    Комментировать