Вектор, который может иметь 3 разных типа данных C++

Я пытаюсь создать вектор на С++, который может хранить 3 разных типа данных. Я не хочу использовать библиотеку boost. Что-то типа:

vector<type1, type2, type3> vectorName; 

Нужно ли делать шаблон? И если да, то как бы я это сделал?


person Jonny Forney    schedule 06.10.2014    source источник
comment
Вам, вероятно, понадобится какой-то тип класса/контейнера-оболочки для хранения ссылок на разные типы. Аналогичный вопрос здесь   -  person Abbath    schedule 06.10.2014
comment
См., возможно, tuple. Векторы (также известные как списки) представляют собой последовательности из одного или нескольких однородных элементов. Кортежи — это конечные (и фиксированного размера) наборы возможных разнородных элементов.   -  person user2864740    schedule 06.10.2014
comment
Вы хотите иметь вектор, каждый элемент которого содержит все 3 типа? Или вектор, который может содержать один тип или другие?   -  person Galik    schedule 06.10.2014
comment
Я не хочу использовать библиотеку boost. Это позор, так как он содержит именно то, что вам нужно.   -  person Mike Seymour    schedule 06.10.2014
comment
В зависимости от того, что именно вам нужно, вы можете подумать о написании структуры POD для объединения ваших типов вместе. Затем просто сделайте из них вектор.   -  person Kevin    schedule 06.10.2014


Ответы (4)


РЕДАКТИРОВАТЬ: начиная с С++ 17, стандартная библиотека теперь включает шаблон класса std:: вариант, который очень похож на ранее существовавшие решения в boost. variant — это типобезопасная альтернатива объединениям, которая позволяет объединять несколько типов с помощью отношения или, например, std::variant<type1, type2, typ3> содержит либо type1, либо type2, либо type3.. Он может быть составлен с std::vector, чтобы получить именно то, что вы описали:

std::vector<std::variant<type1, type2, type3>> vectorName; 

Однако std::variant вводит некоторые ограничения. Например, он не может содержать ссылочные или массивные типы, а доступ к базовому типу (например, type1 или type2) возможен только с помощью кода шаблона. Если std::variant не обеспечивает нужного вам поведения, продолжайте читать, чтобы узнать о более сложном, но более универсальном подходе, который также имеет преимущество работы в любой версии C++.

ОРИГИНАЛЬНЫЙ ОТВЕТ:

Самый простой способ хранить несколько типов в одном векторе — сделать их подтипами родительского класса, обернув желаемые типы в классы, если они уже не являются классами.

class Parent {
  // Anything common to the types should be declared here, for instance:
  void print() { // Make this virtual if you want subclasses to override it
     std::cout << "Printing!";
  }

  virtual ~Parent(); //virtual destructor to ensure our subclasses are correctly deallocated
};

class Type1 : public Parent {
    void type1method();
};

class Type2 : public Parent {
    void type2Method();
};


class Type3 : public Parent {
    void type3Method();
};

Затем вы можете создать вектор из Parent указателей, которые могут хранить указатели на дочерние типы:

std::vector<Parent*> vec;

vec.push_back(new Type1);
vec.push_back(new Type2);
vec.push_back(new Type3);

При доступе к элементам непосредственно из вектора вы сможете использовать только элементы, принадлежащие Parent. Например, вы можете написать:

vec[0]->print();

Но нет:

vec[0]->type1Method();

Поскольку тип элемента был объявлен как Parent*, а тип Parent не имеет члена с именем type1Method.

Если вам нужно получить доступ к членам, специфичным для подтипа, вы можете преобразовать указатели Parent в указатели подтипа следующим образом:

Parent *p = vec[0];

Type1 *t1 = nullptr;
Type2 *t2 = nullptr;
Type3 *t3 = nullptr;

if (t1 = dynamic_cast<Type1*>(p)) {
    t1->type1Method();
}
else if (t2 = dynamic_cast<Type2*>(p)) {
    t2->type2Method();
}
else if (t3 = dynamic_cast<Type3*>(p)) {
    t3->type3Method();
}

Хотя обычно считается, что лучше избегать такого явного ветвления типов и вместо этого полагаться на виртуальные методы.

Обязательно удалите указатели, прежде чем удалять их из вектора, если вы используете динамическое размещение, как я сделал в примере выше. В качестве альтернативы используйте интеллектуальные указатели (вероятно, std::unique_ptr) и позвольте вашей памяти позаботиться о себе:

std::vector<std::unique_ptr<Parent>> vec;
person ApproachingDarknessFish    schedule 06.10.2014
comment
Вы ничего не удалили. Почему бы не использовать вектор unique_ptr, чтобы он удалял его автоматически? Родителю нужен виртуальный деструктор. - person Neil Kirk; 06.10.2014
comment
@NeilKirk Да, забыл про виртуальный деструктор. Я также упомяну умные указатели. - person ApproachingDarknessFish; 06.10.2014
comment
Хорошо, я могу добавить разные типы в свой вектор, но я не могу получить доступ к определенным методам в типах. Как я могу получить доступ к этим различным методам? Например, если я хочу вызвать vec[i].print(); Спасибо за помощь! - person Jonny Forney; 06.10.2014
comment
@JonathanForney Любые методы, которые можно применить ко всем трем типам, должны быть в родительском типе. Я добавлю пример. - person ApproachingDarknessFish; 06.10.2014

Я пытаюсь создать вектор на С++, который может хранить 3 разных типа данных.

Ответ здесь действительно зависит от конкретного варианта использования:

  1. Если объекты каким-то образом связаны и чем-то похожи — создайте базовый класс и выведите из него все классы, затем сделайте векторное хранилище unique_ptrs родительским классом (подробности см. в ответе ApproachingDarknessFish),

  2. Если все объекты относятся к фундаментальным (то есть встроенным) типам, используйте union, который группирует типы, и определите vector<yourUnionType>,

  3. Если объекты неизвестного типа, но вы уверены, что они имеют схожий интерфейс, создайте базовый класс и выведите из него шаблонный дочерний класс (template <typename T> class container: public parent{};) и создайте vector<unique_ptr<parent>>, как в первом случае,

  4. Если объекты относятся к типам, которые по какой-либо причине не могут быть связаны (например, vector хранит int, std::string и yourType), соедините их через union, как в 2. Или - еще лучше...

...если у вас есть время и вы хотите чему-то научиться - посмотрите, как реализован boost::any, и попробуйте реализовать его самостоятельно, если уж совсем не хочется пользоваться самой библиотекой. Это не так сложно, как может показаться.

person Paweł Stawarz    schedule 06.10.2014
comment
Почему ограничение в варианте 2? - person MSalters; 06.10.2014
comment
@MSalters, потому что я твердо верю, что если это не так, то получение базового класса является более объектно-ориентированным решением. Кроме того, использование союзов в vector не особенно удобно - вам нужно дополнительно хранить вторичный вектор, который будет содержать тип, который хранится в каждом объединении, чтобы вы правильно обращались к нему и не получали мусор. - person Paweł Stawarz; 06.10.2014
comment
Необходимость в векторе дискриминантов необходима и для фундаментальных типов, так что это не повод проводить различие. И, как вы заметили, отсутствие общего базового класса является реальной проблемой, так почему бы в этом случае не придерживаться варианта 2 вместо уродливого хака в 4? - person MSalters; 06.10.2014
comment
@MSalters, если честно, ты прав. Я отредактирую вариант 4. Думаю, я перестарался. - person Paweł Stawarz; 06.10.2014

вы можете использовать std::any, хранить свои объекты в векторе как любые, а когда вы их вытаскиваете, используйте type() == typeid(mytype)

https://en.cppreference.com/w/cpp/utility/any

Это только для С++ 17 и выше.

person stephane k.    schedule 09.12.2018

Это должен быть вектор? Вы можете просто рассмотреть связанный список универсального типа, затем перебрать этот список, использовать typeid(), чтобы выяснить тип данных узла, и получить данные с помощью функции node.get().

person FunkMasterP    schedule 14.05.2018