С++ Наведите несколько типов на вектор

Примечание: Я знаю, что подобные вопросы были заданы на SO раньше, но я не нашел их полезными или очень ясными.

Второе примечание: В рамках этого проекта/назначения я стараюсь избегать сторонних библиотек, таких как Boost.

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

vector<something magical to hold various types> vec;
int x = 3;
string hi = "Hello World";
MyStruct s = {3, "Hi", 4.01};

vec.push_back(x);
vec.push_back(hi);
vec.push_back(s);

Я слышал, что vector<void*> может работать, но потом становится сложно с распределением памяти, и тогда всегда существует вероятность того, что некоторые части в соседней памяти могут быть непреднамеренно переопределены, если значение, вставленное в определенный индекс, больше, чем ожидалось.

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

Есть ли способ, которым я могу безопасно выполнить задачу, которую я продемонстрировал в своем примере кода?

Спасибо за ваше время.

Ответы

Ответ 1

Для этого вам определенно понадобится класс-оболочка, чтобы каким-то образом скрыть информацию о типе объектов из вектора.

Вероятно, также хорошо, что этот класс генерирует исключение, когда вы пытаетесь получить Type-A обратно, когда вы ранее хранили тип B в нем.

Вот часть класса Holder из одного из моих проектов. Вероятно, вы можете начать здесь.

Примечание: из-за использования неограниченных объединений это работает только на С++ 11. Более подробную информацию об этом можно найти здесь: Что такое Неограниченные союзы, предложенные в С++ 11?

class Holder {
public:
    enum Type {
        BOOL,
        INT,
        STRING,
        // Other types you want to store into vector.
    };

    template<typename T>
    Holder (Type type, T val);

    ~Holder () {
        // You want to properly destroy
        // union members below that have non-trivial constructors
    }

    operator bool () const {
        if (type_ != BOOL) {
           throw SomeException();
        }
        return impl_.bool_;
    }
    // Do the same for other operators
    // Or maybe use templates?

private:
    union Impl {
        bool   bool_;
        int    int_;
        string string_;

        Impl() { new(&string_) string; }
    } impl_;

    Type type_;

    // Other stuff.
};

Ответ 2

Объекты, удерживаемые символом std::vector<T>, должны быть однородного типа. Если вам нужно поместить объекты другого типа в один вектор, вам нужно как-то стереть их тип и сделать их похожими. Вы можете использовать моральный эквивалент boost::any или boost::variant<...>. Идея boost::any состоит в том, чтобы инкапсулировать иерархию типов, сохраняя указатель на базу, но указывая на шаблонную производную. Очень грубая и неполная схема выглядит примерно так:

#include <algorithm>
#include <iostream>

class any
{
private:
    struct base {
        virtual ~base() {}
        virtual base* clone() const = 0;
    };
    template <typename T>
    struct data: base {
        data(T const& value): value_(value) {}
        base* clone() const { return new data<T>(*this); }
        T value_;
    };
    base* ptr_;
public:
    template <typename T> any(T const& value): ptr_(new data<T>(value)) {}
    any(any const& other): ptr_(other.ptr_->clone()) {}
    any& operator= (any const& other) {
        any(other).swap(*this);
        return *this;
    }
    ~any() { delete this->ptr_; }
    void swap(any& other) { std::swap(this->ptr_, other.ptr_); }

    template <typename T>
    T& get() {
        return dynamic_cast<data<T>&>(*this->ptr_).value_;
    }
};

int main()
{
    any a0(17);
    any a1(3.14);
    try { a0.get<double>(); } catch (...) {}
    a0 = a1;
    std::cout << a0.get<double>() << "\n";
}

Ответ 3

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

Вот пример, если все, что мы хотим сделать, это напечатать объекты на консоли:

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class any_type
{
public:
   virtual ~any_type() {}
   virtual void print() = 0;
};

template <class T>
class concrete_type : public any_type
{
public:
   concrete_type(const T& value) : value_(value)
   {}

   virtual void print()
   {
      std::cout << value_ << '\n';
   }
private:
   T value_;
};

int main()
{
   std::vector<std::unique_ptr<any_type>> v(2);

   v[0].reset(new concrete_type<int>(99));
   v[1].reset(new concrete_type<std::string>("Bottles of Beer"));

   for(size_t x = 0; x < 2; ++x)
   {
      v[x]->print();
   }

   return 0;
}