Можно ли иметь специализированную иерархию классов для шаблонизированного базового класса, но при этом использовать преимущества полиморфизма между ними? - PullRequest
1 голос
/ 21 февраля 2012

У меня есть базовый класс, представляющий элемент с некоторыми общими свойствами (имя, несколько флагов и т. Д.):

class AbstractItem;
class MacroDefinition : public AbstractItem;

У меня также есть шаблонизированный класс, который управляет коллекциями этих элементов, также заботясь о них.общих функций, таких как загрузка их из файлов XML на диск:

template <class ItemT>
class AbstractItemManager
{
public:
    AbstractItemManager();
    ItemT* GetAt(int index);
    vector<ItemT*> Get(...);
private:
    vector<ItemT*> mItems;
};

Для любого данного типа AbstractItem я могу создать класс менеджера этого подходящего типа, обработать базовую функциональность для меня изатем функциональность слоя, относящаяся к этому типу, поверх этого:

class MacroManager : public AbstractItemManager<MacroDefinition>
{
public:
    MacroManager():AbstractItemManager<MacroDefinition>();
};

Тот факт, что класс менеджера принимает тип элемента в качестве параметра шаблона, означает, что я могу совершать подобные вызовы, как в MacroManager, так и извне,и получать предметы соответствующего типа без необходимости слепо разыгрывать указатели повсюду.

MacroManager* macroManager = new MacroManager();
Macro* macro = macroManager->GetAt(2);

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

class FavoriteAbstractItemList
{
public:
    FavoriteAbstractItemList(AbstractItemManager* manager)
    :mManager(manager)
    {
        vector<AbstractItem*> items = mManager->Get(...);
        ...
    }

private:
    AbstractItemManager* mManager;
};

Следовательно:

FavoriteAbstractItemList* list = new FavoriteAbstractItemList(macroManager);

Конечно, это неверно, потому что я не предоставляю аргумент шаблона дляAbstractItemManager когда я использую его в FavoriteAbstractItemList.Поскольку у моих подклассов менеджера (MacroManager и т. Д.) Есть все разные типы ItemT, я застрял здесь.

Я представляю, что мог бы немного изменить свою иерархию классов, и это сработало бы:

template<class ItemT>
class AbstractItemManager_Base;

class AbstractItemManager : public AbstractItemManager_Base<AbstractItem>;

class MacroManager : public AbstractItemManager;

Но тогда аргумент шаблона ItemT будет установлен в камне как AbstractItem в MacroManager и т. Д., Поэтому мне придется явно привести все элементы в пределах MacroManager к Macro ипозаботьтесь о том, чтобы к нему были добавлены только элементы типа Macro.

Похоже, что это, вероятно, распространенная проблема, но не та, которая имеет прямой ответ.У меня не так уж много практического опыта работы с шаблонами C ++, поэтому я был бы очень признателен за то, чтобы разобраться в этом вопросе.Учитывая компромиссы, которые я представил, каков наиболее разумный способ достичь того, что я ищу?Или я начинаю с неправильного подхода?


Спасибо за все ваши полезные ответы.Я закончил тем, что пошел с решением, которое вы оба предложили.Мне не приходило в голову, что я мог бы использовать тип шаблона для переопределения уже определенного базового типа, но к тому времени я привык к компиляции шаблонов C ++ во время компиляции.

Что касаетсявекторная проблема, к сожалению, но я закончил тем, что выбрал одно из предложенных решений и создал отдельный метод в шаблонном классе, который вызывает исходный метод и помещает все в новый vector<ItemT*> с кучей статических приведений.Я уверен, что это добавляет немного накладных расходов, но это все же гораздо более элегантно, чем мое коленное решение полностью отказаться от шаблонов.Единственное, что я действительно теряю, - это возможность напрямую перебирать mItems в подклассах без приведения от AbstractItem* до Macro* (и т. Д.), Но я, безусловно, могу с этим справиться.

Вотновая иерархия классов, по сути:

class AbstractItemManager
{
public:
    virtual AbstractItem* GetAt(int index);
    vector<AbstractItem*> Get(...);
protected:
    vector<AbstractItem*> mItems;
};

template <class ItemT>
class TemplatizedItemManager : public AbstractItemManager
{
public:
    virtual ItemT* GetAt(int index);
    std::vector<ItemT*> GetItems(...);
};

class MacroManager : public TemplatizedItemManager<Macro>;

Еще раз спасибо!

Ответы [ 2 ]

1 голос
/ 21 февраля 2012

У вас на самом деле две проблемы. Первый - это околоz GetAt. Это простое решение: не шаблонизируйте базу, шаблонируйте производное:

class AbstractItem
{
  // ...
};

class MacroDefintiion:
  public AbstractItem
{
  // ...
};

class AbstractItemMananger
{
public:
  virtual AbstractItem* GetAt(int) = 0;
  // ...
};

template<typename Item> class SpecificAbstractItemManager
{
public:
  Item* GetAt(); // covariant return type
  // ...
};

class MacroManager: public SpecificAbstractItemManager
{
  // ...
};

Второй - ваш Get метод. Это проблематично, потому что std::vector<Derived*> и std::vector<Base*> являются несвязанными классами для C ++, и поэтому вы не можете использовать их для ковариантных типов возвращаемых данных.

Вероятно, лучшее решение здесь - это иметь две функции в производном классе, одна из которых возвращает std::vector<AbstractItem> (унаследованная и переопределяющая функцию базового класса), а другая возвращает std::vector<Item*>.

То есть в AbstractItemManager у вас есть

std::vector<AbstractItem*> Get() = 0;

и SpecificAbstractItemManager<Item>, например,

std::vector<AbstractItem*> Get() { return GetSpecific(); }
std::vector<Item*> GetSpecific();
1 голос
/ 21 февраля 2012
class AbstractItemManager_Base
{
   public:
     virtual AbstractItem* GetAt (int index) = 0;
};

template <class ItemT>
class AbstractItemManager : public AbstractItemManager_Base
{
    ItemT* GetAt (int index); // works if ItemT derives from AbstractItem
};

Теперь вы можете использовать AbstractItemManager_Base в FavoriteAbstractItemList.

Замена vector<ItemT*> Get(...) несколько сложнее. vector<AbstractItem*> несовместимо с vector<ItemT*>, для любого ItemT. Вы можете попытаться создать собственную иерархию контейнеров, такую, что myvector<AbstractItem*> каким-то образом совместим с myvector<ItemT*>; или предоставьте интерфейс ItemManager на основе итератора, чтобы it был контейнером; или просто иметь две отдельные несвязанные функции, одна из которых возвращает vector<ItemT*>, а другая возвращает vector<AbstractItem*>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...