2016-07-05 2 views
1

Мне интересно, есть ли у вас контейнер с объектами с различными параметрами шаблона.Контейнер шаблонных классов без параметра шаблона

Я пытаюсь добиться чего-то вроде этого:

#include <iostream> 
#include <list> 

template <class T> 
class base 
{ 
    public: 
     T val; 
     base(T newVal): val(newVal) {}; 
}; 

class derived : public base<int> 
{ 
    public: 
     derived(int newVal): base(newVal) {}; 
}; 

int main (void) 
{ 
    std::list < base<?> > base_collection; 
    return 0; 
} 

Я хочу, чтобы мой текущий проект, чтобы быть гибким и динамичным, насколько это возможно, с небольшим количеством дополнительного кодирования, когда новый производный класс необходимо, и мой текущий реализация делает важным, чтобы такой список существовал.

Существует ли обычно используемый, полезный и чистый способ достижения именно этого?

+0

«Моя текущая реализация делает важным, чтобы такой [гетерогенный] список существовал». Ой. Это анти-шаблон. Вам нужно отбросить этот проект. –

+0

Нет, вы не можете. 'base ' является * совершенно другим типом * до 'base '. И вы не можете хранить объект производного класса в переменной базового класса в любом случае. – immibis

ответ

2

Возможная реализация будет с помощью двойной диспетчеризации:

#include <iostream> 
#include <list> 

struct visitor; 

struct dispatchable { 
    virtual void accept(visitor &v) = 0; 
}; 

template <class> 
struct base; 

struct visitor { 
    template<typename T> 
    void visit(base<T> &); 
}; 

template <class T> 
struct base: dispatchable { 
    T val; 
    base(T newVal): val(newVal) {}; 
    void accept(visitor &v) override { v.visit(*this); } 
}; 

struct derivedInt : base<int> { 
    derivedInt(int newVal): base(newVal) {}; 
}; 

struct derivedDouble : base<double> { 
    derivedDouble(double newVal): base(newVal) {}; 
}; 

template<> 
void visitor::visit(base<int> &) { 
    std::cout << "int" << std::endl; 
} 

template<> 
void visitor::visit(base<double> &) { 
    std::cout << "double" << std::endl; 
} 

int main (void) { 
    visitor v{}; 
    std::list <dispatchable*> coll; 
    coll.push_back(new derivedInt{42}); 
    coll.push_back(new derivedDouble{.42}); 
    for(auto d: coll) d->accept(v); 
} 

Таким образом, у вас есть только определить специальную функцию, которая имеет дело с новым base<T> типа вы хотите ввести.
В качестве примера, если вы хотите использовать base<char>, вы должны определить:

template<> 
void visitor::visit(base<char> &) { 
    std::cout << "char" << std::endl; 
} 

Обратите внимание, что я должен вы хотите обработать каждую специализацию base<T> по-другому. В противном случае достаточно определить общую функцию-член visitor::visit и отбросить специализации.


Сторона примечания: не используйте голые указатели.
Это пример. В производственном коде вместо этого я бы использовал интеллектуальные указатели.

+0

Это прекрасно, спасибо большое. – reign

+0

Это приемлемо, если существует только одна соответствующая операция для каждого класса, например 'process event'. –

+0

@JohanLundberg OP хочет обработать 'base ' для каждого 'T', не более того. Как он упомянул в комментарии к вашему ответу, он * хочет * использовать 'val'. Таким образом, он может использовать его. Двойная диспетчеризация подходит в этом случае. В чем проблема? – skypjack

3

Непонятно, зачем вам это нужно или какие операции вы намерены выполнять над элементами списка (кстати, вместо этого используйте std-вектор). Я предлагаю вам сделать общий, не шаблонный базовый класс, который наследуется от базового:

struct mainbase { 
    virtual ~mainbase() = default; 
}; 

template <class T> 
class base : public mainbase 
{ 
    public: 
     T val; 
     base(T newVal): val(newVal) {}; 
}; 


class derived : public base<int> 
{ 
    public: 
     derived(int newVal): base(newVal) {}; 
}; 

int main (void) 
{ 
    std::list < std::unique_ptr<mainbase>> > base_collection; 
    return 0; 
} 

В конце концов, если вы собираетесь поместить их все в векторе, вы, скорее всего, потребуются общий набор операций, которые вам может выполнять с этими объектами. Поместите их в mainbase.

Как указывает @BenjaminLindley, вы не можете иметь полиморфизм по значению. Вот почему вы использовали бы указатель (such as unique_ptr): std::unique_ptr<mainbase>.

С C++ 17 есть предложение (на дорожку) для std::any, которое можно было бы использовать вместо этого, но вам все равно придется выполнить конкретный отбор, чтобы получить контент с правильным типом.

+1

Вы хотите сохранить указатели (умные или другие). –

+0

Как бы вы получили доступ к val из объекта mainbase без кастинга? – reign

+0

@BenjaminLindley. Зачем использовать указатели в этом случае? –

Смежные вопросы