2016-03-03 2 views
2

Предположим, у нас есть класс, как следующее:C++: магазин/запомнить тип шаблона класса

class AbstractContainer{ 
    ... 
}; 

template <typename T> 
class Container : public AbstractContainer { 
    T someFunction(); 
}; 

Теперь есть еще один класс, который имеет переменную-член, который должен хранить один из этих контейнеров. Однако тип шаблона не должен быть фиксированным. Поэтому вместо объявления переменной-члена типа Container оно объявляется как тип AbstractContainer*, поэтому указатель на базовый класс. Этот указатель должен иметь возможность хранить все типы классов Container, независимо от параметра шаблона.

class Interface{ 
public: 
    Interface(); 
    void doSth(); 
private: 
    AbstractContainer* container; 
}; 

Давайте предположим, что контейнер выполнен в конструкторе Interface класса, как это:

Interface::Interface(){ 
    if (/* some condition */) 
     this->container = new Container<int>(25); 
    } else { 
     this->container = new Container<float>(25); 
    } 
    //here I'd need to remember of which type container is: int or float 
} 

Так вот мне нужно бы хоть как-то магазин, какой тип мой контейнер (int или float) , Я знаю это в этот момент своей программы, и он полностью детерминирован. Мне нужно хранить его, потому что позже я мог бы отдать свой AbstractContainer* спиной к Container<int>* или Container<float>*, например, в другой функции:

void Interface::doSth(){ 
    //here I have to recall what the type was 
    if(/* type was int */) { 
     dynamic_cast<Container<int>&>(*(this->container)).someFunction(); 
    } else { 
     dynamic_cast<Container<float>&>(*(this->container)).someFunction(); 
    } 
} 

Я думал об использовании enum, который содержит значения для всех других поддерживаемых типы и сохранение типа в дополнительной переменной-члене этого типа перечисления. Тогда я должен был бы сделать оператор switch, который проверяет все различные возможности и выполняет правильный перевод. Однако я задавался вопросом, может ли быть более простой способ.

Что я хотел бы сделать, это сохранить тип, используемый в конструкторе Interface, а затем вызвать его в Interface::doSth.

EDIT: Я должен четко указать, что функция someFunction зависит от параметра шаблона T. Таким образом, это не вариант сделать его виртуальным.

+1

Прежде всего, необходимо ответить, почему 'Интерфейс :: doSth' принимает' 'Контейнер а также 'Container '. Возможно, он вычисляет сумму и не заботится о конкретном базовом типе. В этом случае вы пишете функцию 'virtual double get_sum() const' в' AbstractContainer' и 'переопределяете 'ее в различных' Container '. Есть и другие способы заставить его работать, но выбросить информацию о типе, а затем восстановить его с помощью броска, вероятно, является наихудшим. – nwp

+0

Как насчет этого возвращает один из элементов контейнера. Затем он вернет «int» для одного и «float» для другого контейнера. Я не могу определить функцию в базовом классе, потому что для этого требуется параметр шаблона 'T'. – user1488118

+0

'boost :: variant' и' static_visitor' могут быть альтернативным дизайном. – Jarod42

ответ

2

Вы можете сделать

void Interface::doSth(){ 
    if (auto* c_int = dynamic_cast<Container<int>*>(this->container)) { 
     c_int->someFunction(); 
    } else if (auto* c_float = dynamic_cast<Container<float>*>(this->container)) { 
     c_float->someFunction(); 
    } 
} 

Но почему бы не двигаться someFunction() в базовый класс? и использовать виртуальный метод?

+0

Почему 'auto *' вместо 'auto'? – nwp

+0

Потому что 'someFunction' зависит от параметра шаблона. Это может быть, например, имеют тип возвращаемого типа 'T'. Я изменил вопрос соответствующим образом. Я не совсем уверен, что вы там делали. – user1488118

+0

@nwp: Это только предпочтение стиля: быть явным указателем. – Jarod42

1

Хотя Jarod42's answer лучше, один из способов сделать это является использование typeid:

void doSth() 
{ 
    if (typeid(*container).hash_code() == typeid(Container<int>).hash_code()) 
    { 
     cout << "int" << endl; 
    } 
    else if (typeid(*container).hash_code() == typeid(Container<float>).hash_code()) 
    { 
     cout << "float" << endl; 
    } 
} 

Но вам нужно, по крайней мере виртуальной функции в базовом классе. Например, вы можете создать виртуальный деструктор.

1

Возможно, ваш ответ Curiously recurring template pattern.

template<class T, template<class> class U> 
class AbstractContainer 
{ 
    void interface() 
    { 
     static_cast<U<T> *>(this)->implementation(); 
    } 
}; 

template<class T> 
class Container : public AbstractContainer<T, Container> 
{ 
    void implementation() 
    { 

    } 
}; 
+0

Этот подход действительно выглядит интересным. Однако, я думаю, что это не помогло бы в моем случае. Это связано с тем, что базовый класс зависит от параметра шаблона.Вся мотивация для наследования заключалась в том, что я мог хранить указатель на базовый класс, тогда как фактический объект содержал разные типы. Например, я мог бы иметь «Контейнер » или «Контейнер », и все они могли быть сохранены в «AbstractContainer *», потому что базовый класс не является классом шаблона. – user1488118

0

Другой вариант:

template <class T> 
T doSth(Container<T> &container){ 
    return container.someFunction(); //assume someFunction returns a T 
} 

Вам не нужен базовый класс, ни наследования, ни слепков.

+0

Но тогда я не могу хранить контейнеры с разными типами в классе 'Interface', потому что мне нужно было объявить' container' как 'Container ' ** или ** a 'Container ' – user1488118

+0

@ user1488118 Как насчет что делает его «интерфейсом» 'или' интерфейсом '? – nwp

+0

Это не вариант, потому что в моем случае (который этот вопрос является упрощенной версией) класс интерфейса является фактическим классом пользовательского интерфейса (поэтому Qt-окно). Я не могу просто уничтожить его во время выполнения программы. Однако содержимое контейнера должно быть в состоянии изменить. Пользователь имеет возможность читать в двоичных файлах с диска. Эти файлы могут содержать разные типы данных, и все они должны обрабатываться. – user1488118

0
class AbstactObject { 
public: 
    virtual ~AbstractObject() = 0; 

    virtual AbstractObject &doSomething() = 0; 
} 

template<class T> 
class Object : AbstactObject { 
public: 
    virtual ~Object(); 

    virtual Object<T> &doSomething(); 

    T &get(); 
private: 
    T t; 
} 

class AbstractContainer { 
public: 
    virtual ~AbstractObject() = 0; 

    virtual AbstractObject &doSomething(); 

private: 
    AbstractObject *obj; 
}; 

template<class T> 
class Container : AbstactContainer { 
public: 
    virtual ~Container(); 

    virtual Object<T> &doSomething() { 
    return obj->doSomething(); 
    }; 
} 

class Interface { 
public: 
    Interface(); 
    void doSth(); 
private: 
    AbstractContainer* container; 
}; 


Interface::Interface() { 
    if (/* some condition */) 
    container = new Container<int>(25); 
    } else { 
    container = new Container<float>(25); 
    } 
} 

void Interface::doSth() { 
    auto obj = container->doSomething(); 
    auto val = obj.get();  
} 

, когда вы хотите, чтобы получить значение Т

auto obj = container->doSomething(); 
auto val = obj.get(); 

Пожалуйста, проверьте: ковариантный тип возвращаемого https://en.wikipedia.org/wiki/Covariant_return_type

+0

Обратите внимание: в вашем случае возврат 'doSomething()' является 'void'. Это не работает, если тип возврата - 'T'. – user1488118

+0

Я попытался исправить пример. Сожалею. – mustafagonul

+0

Хм ... это трудно понять. Что относительно '* obj'? Он никогда не инициализируется. – user1488118

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