C++ Виртуальный шаблонный метод
У меня есть абстрактный класс (я знаю, что он не будет компилироваться таким образом, но он предназначен для понимания того, что я хочу сделать):
class AbstractComputation {
public:
template <class T> virtual void setData(std::string id, T data);
template <class T> virtual T getData(std::string id);
};
class Computation : public AbstractComputation {
public:
template <class T> void setData(std::string id, T data);
template <class T> T getData(std::string id, T data);
};
Поэтому, когда я звоню setData<double>("foodouble", data)
Я хочу, чтобы двойник определялся как foodouble
(внутренний механизм, который не является главной проблемой здесь), чтобы установить двойные данные.
Так как это сделать?
Я думаю, что может быть что-то среднее, набрав что-то вроде virtual void setData<double>(std::string id, double data)
но я не знаю как это сделать.
6 ответов
Проблема в том, что вы не можете легко смешать статический полиморфизм времени (шаблоны) с полиморфизмом времени исполнения. Причина, по которой язык запрещает конкретную конструкцию в вашем примере, состоит в том, что существуют потенциально бесконечные различные типы, которые могут создавать экземпляры вашей функции-члена шаблона, и это, в свою очередь, означает, что компилятору придется генерировать код для динамической диспетчеризации этих многих типов, что невыполнимо.
Здесь можно сделать разные вещи, чтобы обойти ограничение, в основном либо убрать статический или динамический полиморфизм. Удаление динамического полиморфизма из уравнения может быть сделано путем предоставления типа, который не является производным от, для хранения <key,value>
сопоставления, а затем предлагая шаблон, который разрешает это только на базовом уровне:
class AbstractComputation {
public:
template <typename T>
void setData( std::string const & id, T value ) {
m_store.setData( id, value );
}
template <typename T>
T getData( std::string const & id ) const {
return m_store.getData<T>( id );
}
protected:
ValueStore m_store;
};
Теперь производные классы могут получить доступ к ValueStore
от основания и нет необходимости в полиморфизме. (Это также может быть сделано путем реализации функциональности непосредственно в AbstractComputation
но, наверное, имеет смысл разделять проблемы)
Другой вариант - сохранить полиморфизм во время выполнения, но удалить статический полиморфизм. Это можно сделать, выполнив стирание типа в базовом классе, а затем отправив соответствующую (не шаблонную) функцию, которая принимает аргументы со стиранием типа. Простейшая версия этого просто использует boost::any
:
class AbstractComputation {
public:
template <typename T>
void setData( std::string const & id, T value ) {
setDataImpl( id, boost::any( value ) );
}
template <typename T>
T getData( std::string const & id ) const {
boost::any res = getDataImpl( id );
return boost::any_cast<T>( res );
}
protected:
virtual void setDataImpl( std::string const & id, boost::any const & value ) = 0;
virtual boost::any getDataImpl( std::string const & id ) const = 0;
};
То, как стирание типов реализовано под капотом, интересно, но за рамками здесь важная часть заключается в том, что boost::any
является конкретным (не шаблонным) типом, который может хранить любой тип внутри себя, используя стирание типов в аргументах, и в то же время обеспечивает безопасный тип поиска данных.
В некоторых случаях может быть достаточно переместить шаблон с уровня метода на уровень класса, например:
#include <iostream>
template<typename T>
class AbstractComputation {
public:
virtual void setData(std::string id, T data)
{
std::cout << "base" << std::endl;
}
};
template<typename T>
class Computation : public AbstractComputation<T> {
public:
virtual void setData(std::string id, T data)
{
std::cout << "derived" << std::endl;
}
};
int main()
{
AbstractComputation<int> *x = new Computation<int>();
x->setData("1", -1);
delete x;
return 0;
}
Во-первых, вы не можете иметь virtual
шаблонные функции. Поскольку шаблоны разрешаются во время компиляции, virtual
не будет работать, так как компилятор не будет знать, какой шаблон выбрать. Смотрите здесь, для получения дополнительной информации об этом.
Вы можете, вероятно, использовать boost::any
в твоем случае.
virtual void setData(std::string id, boost::any data);
Это обертка, которая может заключать в капсулу почти все.
Если вы заранее знаете список возможных типов, препроцессор может помочь:
#define MY_CLASSES MYTYPE(int) MYTYPE(float) MYTYPE(double)
class AbstractComputation {
public:
# define MYTYPE(T) virtual void setData(std::string id, T data)=0;\
virtual void getData(std::string id, T& dst_data)=0;
MY_CLASSES
# undef MYTYPE
};
class Computation : public AbstractComputation {
public:
# define MYTYPE(T) virtual void setData(std::string id, T data){std::cout<<"writing: "<<data<<std::endl;}\
virtual void getData(std::string id, T& dst_data){dst_data=0;/*put your actual implementation here*/}
MY_CLASSES
# undef MYTYPE
};
Если вы не знаете полного списка возможных типов, возможно, ваша проблема неразрешима. Стирание типа, как упоминалось другими, также может помочь... но не при всех обстоятельствах.
Использование boost::any
принять данные, а затем, когда вы на самом деле установите, возьмите из него правильный тип.