C ++ альтернативы указателям void * (это не шаблоны) - PullRequest
7 голосов
/ 03 октября 2008

Похоже, у меня было фундаментальное недоразумение о C ++: <</p>

Мне нравится полиморфный контейнерный раствор. Спасибо ТАК, что обратили на это мое внимание:)


Итак, нам нужно создать относительно общий тип объекта контейнера. Также происходит инкапсуляция некоторой бизнес-логики. Однако нам нужно хранить в этом контейнере практически произвольные данные - от простых типов данных до сложных классов.

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

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

Подводя меня к теме вопроса: самое абстрактное, я представляю, что у меня может быть чисто виртуальный интерфейс «Контейнер», который имеет что-то вроде «push (void * data) и pop (void * data)» ( для записи, я на самом деле не пытаюсь реализовать стек).

Однако мне не очень нравится void * на верхнем уровне, не говоря уже о том, что подпись будет меняться каждый раз, когда я хочу добавить ограничение на тип данных, с которыми может работать конкретный контейнер.

Подведение итогов: у нас есть относительно сложные контейнеры, которые имеют различные способы извлечения элементов. Мы хотим иметь возможность варьировать ограничения на элементы, которые могут входить в контейнеры. Элементы должны работать с несколькими типами контейнеров (при условии, что они соответствуют ограничениям данного конкретного контейнера).

Редактировать: Я должен также упомянуть, что сами контейнеры должны быть полиморфными. Это моя основная причина отказа от использования шаблонного C ++.

Итак, должен ли я отказаться от своей любви к интерфейсам типа Java и использовать шаблоны? Должен ли я использовать void * и все статически разыграть? Или я должен пойти с пустым определением класса «Элемент», который ничего не объявляет, и использовать его в качестве моего класса верхнего уровня в иерархии «Элемент»?

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

Ответы [ 8 ]

13 голосов
/ 03 октября 2008

Вы можете использовать стандартный контейнер boost :: any , если вы храните в контейнере действительно произвольные данные.

Звучит так, как будто вы предпочли бы что-то вроде boost :: ptr_container , где все, что может быть сохранено в контейнере, должно происходить от некоторого базового типа, а контейнер Сам может дать вам только ссылку на базовый тип.

5 голосов
/ 03 октября 2008

Простая вещь заключается в том, чтобы определить абстрактный базовый класс с именем Container и подклассировать его для каждого вида предметов, которые вы можете хранить. Затем вы можете использовать любой стандартный класс коллекции (std::vector, std::list и т. Д.) Для хранения указателей на Container. Имейте в виду, что, поскольку вы будете хранить указатели, вам придется обрабатывать их распределение / освобождение.

Однако тот факт, что вам нужна одна коллекция для хранения объектов такого разного типа, свидетельствует о том, что что-то может быть не так с дизайном вашего приложения. Может быть, лучше пересмотреть бизнес-логику, прежде чем внедрять этот универсальный контейнер.

5 голосов
/ 03 октября 2008

Полиморфизм и шаблоны очень хорошо играют вместе, если вы используете их правильно.

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

Что касается контейнерных интерфейсов: в зависимости от вашего дизайна, возможно, вы также сможете сделать их шаблонными, и тогда у них будут методы типа void push(T* new_element). Подумайте, что вы будете знать об объекте, когда захотите добавить его в контейнер (неизвестного типа). Откуда придет объект? Функция, которая возвращает void*? Вы знаете, что это будет сравнимо? По крайней мере, если все классы сохраненных объектов определены в вашем коде, вы можете заставить их всех наследовать от общего предка, скажем, Storable, и использовать Storable* вместо void*.

Теперь, если вы видите, что объекты всегда будут добавляться в контейнер с помощью метода, подобного void push(Storable* new_element), тогда действительно не будет никакой дополнительной ценности в превращении контейнера в шаблон. Но тогда вы узнаете, что в нем должны храниться Storables.

3 голосов
/ 03 октября 2008

Может быть, у вас нет корневого класса Container, который содержит элементы:

template <typename T>
class Container
{
public: 

   // You'll likely want to use shared_ptr<T> instead.
   virtual void push(T *element) = 0;
   virtual T *pop() = 0;
   virtual void InvokeSomeMethodOnAllItems() = 0;
};

template <typename T>
class List : public Container<T>
{
    iterator begin();
    iterator end();
public:
    virtual void push(T *element) {...}
    virtual T* pop() { ... }
    virtual void InvokeSomeMethodOnAllItems() 
    {
       for(iterator currItem = begin(); currItem != end(); ++currItem)
       {
           T* item = *currItem;
           item->SomeMethod();
       }
    }
};

Эти контейнеры затем могут быть полиморфно переданы:

class Item
{
public:
   virtual void SomeMethod() = 0;
};

class ConcreteItem
{
public:
    virtual void SomeMethod() 
    {
        // Do something
    }
};  

void AddItemToContainer(Container<Item> &container, Item *item)
{
   container.push(item);
}

...

List<Item> listInstance;
AddItemToContainer(listInstance, new ConcreteItem());
listInstance.InvokeSomeMethodOnAllItems();

Это дает вам интерфейс Контейнера безопасным для типов универсальным способом.

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

class Item
{
public:
  virtual void SomeMethod() = 0;
  typedef int CanBeContainedInList;
};

template <typename T>
class List : public Container<T>
{
   typedef typename T::CanBeContainedInList ListGuard;
   // ... as before
};
3 голосов
/ 03 октября 2008

Во-первых, шаблоны и полиморфизм - это ортогональные понятия, и они хорошо играют вместе. Далее, почему вы хотите конкретную структуру данных? А как насчет STL или структур данных надстройки (в частности, указатель содержит ) не работает для вас.

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

1 голос
/ 03 октября 2008

Возможно, вы также захотите проверить Библиотека проверки концепции (BCCL) , которая предназначена для предоставления ограничений на параметры шаблона шаблонных классов, в данном случае, ваших контейнеров.

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

1 голос
/ 03 октября 2008

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

Конечно, это будет означать, что вам нужно будет обернуть примитивные типы данных и в производные классы. Если бы вы пересмотрели использование шаблонов в целом, то здесь я бы использовал шаблоны. Создайте один производный класс из базы, который является шаблоном, и используйте его для примитивных типов данных (и других, где вам не нужно больше функциональности, чем предусмотрено шаблоном).

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

0 голосов
/ 03 октября 2008

Вы не могли бы отказаться от Java-подобных интерфейсов и использовать шаблоны. Предложение Джоша универсального базового шаблона Container, безусловно, позволит вам полиморфно передавать контейнеры и их дочерние элементы, но кроме того, вы, безусловно, можете реализовать интерфейсы как абстрактные классы, чтобы они содержали элементы. Нет причины, по которой вы не могли бы создать абстрактный класс IComparable, как вы предлагали, чтобы у вас могла быть полиморфная функция следующим образом:

class Whatever
{
   void MyPolymorphicMethod(Container<IComparable*> &listOfComparables);
}

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

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