Вопрос C ++ Design: передача функций через шаблоны? - PullRequest
1 голос
/ 09 марта 2011

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

У меня есть ряд типов таблиц данных (производных от абстрактного класса DataTable), которые мне нужно хранить. Я написал абстрактный класс «DataTableIndex», в котором хранятся векторные DataTable * и который выполняет общую работу, общую для всех DataTableIndexes - выполняет поиск, реализует шаблон Proxy, поэтому таблицы загружаются только при необходимости, проверяется на наличие ошибок и т. Д.

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

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

class DataTableIndex
{  
  // various functions to implement lookup, Proxy and error checking
  //   functionality common to all DataTableIndexes.
  // This code needs access to _lookupTable and _theTables

  DataTable* getTable( int tableNum );

private:
  // These functions call the appropriate user interface function for loading
  //   a table of the subclass' type.
  //   They are called by more general non-virtual public functions
  virtual bool loadIndex( vector<LookupEntry*>& returnLookupTable ) = 0;
  virtual DataTable* loadTable( int tableNum ) = 0;

  vector<LookupEntry*> _lookupTable;
  vector<DataTable*>   _theTables;

  UserInterface* UI;
};  

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

class TableTypeA_Index : public DataTableIndex
{
  virtual bool loadIndex( vector<LookupEntry*>& returnLookupTable )
  {
     return UI->loadTableAIndex( _lookupTable );
  }

  virtual DataTable* loadTable( int tableNum )  
  { 
     return UI->loadTableTypeA( _lookupTable[ tableNum ] );
  }

};

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

Другая причина, по которой я хотел бы использовать шаблоны, заключается в том, что я не хочу постоянно приводить DataTable * к фактическому типу таблицы. Несмотря на то, что я знаю во время компиляции, какой тип таблицы это должен быть, я чувствую, что должен использовать dynamic_cast <> для проверки ошибок, но я не хочу, чтобы вызывающая функция getTable () должна была делать это каждый раз (это часто вызывается).

Мое идеальное решение: 1) обобщить класс DataTableIndex в шаблон, используя параметры шаблона, заменяющие LookupEntry * и DataTable * в _lookupTable и _theTables. Это устранит кастинг.

2) отобразить соответствующие функции пользовательского интерфейса для загрузки нужного типа таблицы без подклассов.

Так что в основном я хотел бы, чтобы этот класс выглядел так (как-то)

DataTableIndex< LookupEntryTypeAMatlab, 
                TableTypeA, 
                loadTableAIndex(),
                loadTableTypeA() > theTypeAIndex;

Я немного подумал о классах политики, но у меня сложилось впечатление, что в этом случае я просто перенесу подклассы на что-то другое.

Ответы [ 2 ]

1 голос
/ 09 марта 2011

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

class LoadTableStrategy {
public:
    virtual bool loadIndex( vector<LookupEntry*>& returnLookupTable ) = 0;
    virtual DataTable* loadTable( int tableNum ) = 0;
};

Этот класс должен быть подклассом для каждого типа таблицы. DataTableIndex получит экземпляр LoadTableStrategy в конструкторе и будет использовать его для загрузки данных вместо частных виртуальных функций.

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

0 голосов
/ 09 марта 2011

Если вы хотите придерживаться шаблонов, вы можете превратить TableTypeA_Index в шаблон, однако вам придется передавать указатели на функции-члены UserInterface в конструктор.Например:

typedef bool (UserInterface::*LoadTableIndexFP) (vector<LookupEntry*>&);
typedef DataTable* (UserInterface::*LoadTableTypeFP) (LookupEntry*);

template<class TABLE>
class TableType_Index : public DataTableIndex
{
public:
    TableType_Index (LoadTableIndexFP loadTableIndexFP, LoadTableTypeFP loadTableTypeFP)
        : loadTableIndexFP (loadTableIndexFP)
        , loadTableTypeFP (loadTableTypeFP)
    {
    }

    virtual bool loadIndex( vector<LookupEntry*>& returnLookupTable )
    {
        return (UI->*loadTableIndexFP) (returnLookupTable);
    }

    virtual DataTable* loadTable( int tableNum )
    {
        return (UI->*loadTableTypeFP) (_lookupTable[ tableNum ]);
    }

private:
    LoadTableIndexFP loadTableIndexFP;
    LoadTableTypeFP loadTableTypeFP;
};

int main (int argc, char* argv[])
{
    TableType_Index<TableA> (&UserInterface::loadTableAIndex, &UserInterface::loadTableTypeA);

    return 0;
}

Я не добавил LookupEntryTypeAMatlab в качестве параметра шаблона, так как из вашего определения TableTypeA_Index не ясно, какова будет его роль.

Обратите внимание, чтоальтернатива передаче ptr-to-mem-funs в ctor будет иметь класс признаков для типа таблицы:

template<typename T>
struct TableTraits
{
};

template<>
struct TableTraits<TableA>
{
    static LoadTableIndexFP loadTableIndexFP;
    static LoadTableTypeFP loadTableTypeFP;
};

LoadTableIndexFP TableTraits<TableA>::loadTableIndexFP = &UserInterface::loadTableAIndex;
LoadTableTypeFP TableTraits<TableA>::loadTableTypeFP = &UserInterface::loadTableTypeA;

template<class TABLE>
class TableType_Index : public DataTableIndex
{
public:
    virtual bool loadIndex( vector<LookupEntry*>& returnLookupTable )
    {
        return (UI->*TableTraits<TableA>::loadTableIndexFP) (returnLookupTable);
    }

    virtual DataTable* loadTable( int tableNum )
    {
        return (UI->*TableTraits<TableA>::loadTableTypeFP) (_lookupTable[ tableNum ]);
    }
};

Хотя ни один из подходов не идеален ...

...