Вектор, който може да има 3 различни типа данни C++

Опитвам се да направя вектор в C++, който може да съхранява 3 различни типа данни. Не искам да използвам библиотеката за усилване. Нещо като:

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
Не искам да използвам библиотеката за усилване. Жалко, тъй като има точно това, което искате.   -  person Mike Seymour    schedule 06.10.2014
comment
В зависимост от това от какво точно се нуждаете, можете да помислите за писане на POD структура, за да обедините вашите типове заедно. След това просто направете вектор от тях.   -  person Kevin    schedule 06.10.2014


Отговори (4)


РЕДАКТИРАНЕ: от C++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

Опитвам се да направя вектор в C++, който може да съхранява 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

Това обаче е само за C++17 нататък.

person stephane k.    schedule 09.12.2018

Трябва ли да е вектор? Може просто да помислите за свързан списък от общ тип, след което да преминете през този списък, да използвате typeid(), за да разберете типа данни на възела и да изтеглите данните с функция node.get().

person FunkMasterP    schedule 14.05.2018