Вы спрашиваете о динамических функциях, но ваша главная проблема не в этом, а скорее здесь:
Возвращаемые значения некоторых функций, предоставляемых через этот интерфейс, определяются входным параметром sKeyName.
C ++ - это статически типизированный язык, и это означает, что у вас не может быть возвращаемого типа для функции, которая зависит от значения аргументов. Игнорируя на данный момент наследование, код, который вы представили, требует, чтобы пользователь определил тип возвращаемого массива независимо из переданных аргументов:
struct SimpleDataAccess {
template <typename T>
array2d<T>* get_data( std::string const & which ) {
return new array2d<T>();
}
};
int main() {
SimpleDataAccess accessor;
array2d<int> = accessor.get<int>( "int" ); // <int> at the place of call fixes
// the return type, not "int" !
}
Теперь, если вы готовы жить с этим (то есть вызывающая сторона узнает и установит тип возвращаемого значения), существуют различные способы обеспечения обходных путей для вашей конкретной проблемы языка, не допускающие шаблонные виртуальные функции. Первое, что приходит на ум, приятно, поскольку оно также следует идиоме NVI (и показывает ее важность): обеспечить не виртуальный общедоступный шаблонный метод доступа к данным и реализовать его в терминах виртуальной функции фиксированного возвращаемого типа.
class DataAccessor {
virtual Type get_data_impl( std::string const & ) = 0;
public:
template <typename T>
array2d<T>* get_data( std::string const & which ) {
Type tmp = get_data_impl( which );
return convert( tmp );
}
};
Предполагая, что мы можем решить, что такое Type
и convert
, у нас есть решение. Это хороший пример идиомы NVI: интерфейс, предлагаемый пользователями (общедоступный, не виртуальный), отличается от интерфейса, необходимого для расширений (частный, виртуальный). Два контракта различаются, ваши пользователи требуют, чтобы вы указали конкретный конкретный экземпляр array2d
, но язык не позволяет вам требовать того же контракта от ваших расширений, но это не проблема, поскольку они отличаются интерфейсы.
Теперь вернемся к Type
и convert
. Эти два взаимосвязаны, и есть разные подходы, которым вы можете следовать. Самым простым для реализации было бы иметь класс array2d_base
, из которого все array2d<T>
были получены (предоставив виртуальный деструктор, вы включили RTTI):
struct array2d_base {
virtual ~array2d_base() {}
};
template <typename T>
class array2d : public array2d_base {
// implementation
};
// Type == array2d_base*
// convert == dynamic_cast< array2d<T>* >
template <typename T>
array2d<T>* DataAccessor::get_data( std::string const & s ) {
return dynamic_cast< array2d<T>* >( get_data_impl( s ) );
}
Если вы не можете расширить или изменить класс array2d
, то вы можете достичь аналогичного результата с помощью стирания типа. Это будет иметь преимущество, заключающееся в том, что RTTI не требуется в array2d
, а только в поддержке удаления типа. Простейшей такой реализацией было бы использование boost::any
во внутреннем интерфейсе:
// Type == boost::any
// convert == boost::any_cast< array2d<T>* >
template <typename T>
array2d<T>* DataAccessor::get_data( std::string const & s ) {
boost::any tmp = get_data_impl(s);
return boost::any_cast< array2d<T>* >( tmp );
}