Как обойти шаблонную виртуальную функцию для достижения моей цели? - PullRequest
2 голосов
/ 02 июля 2011

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

По сути, у меня есть абстрактный базовый класс, определяющий интерфейс для набора производных классов. Возвращаемые значения некоторых функций, предоставляемых через этот интерфейс, определяются входным параметром sKeyName. Мой предполагаемый код размещен ниже.

class DataAccess
{ 
    public: 
        template <class T> 
        virtual const Array2D<T>* GetData(std::string sKeyName) const = 0;

        template <class T>  
        virtual Array2D<T>* GetData(std::string sKeyName) = 0; 
}; 

Может кто-нибудь дать мне обходной путь для получения этой функциональности? Любая помощь приветствуется. Заранее спасибо.

Ответы [ 5 ]

4 голосов
/ 02 июля 2011

Что вам нужно сделать, это определить отдельный шаблонный интерфейс.

class DataAccess {
    template<typename T> class InnerInteface {
        virtual Array2d<T>* GetData(std::string) = 0;
    };
};
class OHai : public DataAccess, DataAccess::InnerInterface<float> {
};
int main() {
    DataAccess* D = new OHai;
    if (DataAccess::InnerInterface<float>* data = 
       dynamic_cast<DataAccess::InnerInterface<float>>(D)) {
    }
}

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

3 голосов
/ 02 июля 2011

Вы спрашиваете о динамических функциях, но ваша главная проблема не в этом, а скорее здесь:

Возвращаемые значения некоторых функций, предоставляемых через этот интерфейс, определяются входным параметром 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 );
}
0 голосов
/ 02 июля 2011
  • Функции шаблона не могут быть виртуальными.
  • Array2D нужен параметр шаблона.Даже если тип класса шаблона установлен по умолчанию, необходимо <>.
0 голосов
/ 02 июля 2011

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

template< typename T >
struct B
{
  virtual Array2D<T>* GetData(std::string sKeyName) = 0; 
};

struct A : public B< int >
{
  virtual Array2D<int>* GetData(std::string sKeyName)
  {
    // implement
  }
};
0 голосов
/ 02 июля 2011

Пусть все ваши различные типы, которые могут быть возвращены из GetData, наследуются от базового класса и возвращают указатель на этот класс. Пример:

class Data {};
class Array2D: public Data{};
class DataAccess
{ 
    public:
        virtual Data* GetData(std::string sKeyName) const = 0;
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...