Похоже, у меня было фундаментальное недоразумение по поводу C++: <
Мне нравится полиморфный контейнерный раствор. Спасибо, ТАК, что обратил на это мое внимание :)
Итак, нам необходимо создать относительно общий объект типа контейнера. Также случается инкапсуляция некоторой бизнес-логики. Однако нам нужно хранить в этом контейнере практически произвольные данные - все, от примитивных типов данных до сложных классов.
Таким образом, можно сразу перейти к идее шаблонного класса и покончить с этим. Однако я заметил, что полиморфизм C++ и шаблоны плохо сочетаются друг с другом. Поскольку существует некоторая сложная логика, с которой нам придется работать, я бы предпочел просто придерживаться либо шаблонов, либо полиморфизма, а не пытаться бороться с C++, заставляя его делать то и другое.
Наконец, учитывая, что я хочу сделать одно или другое, я бы предпочел полиморфизм. Мне гораздо проще представить ограничения типа «этот контейнер содержит сопоставимые типы» - а-ля java.
Подводя меня к теме вопроса: в наиболее абстрактной форме я предполагаю, что мог бы иметь чистый виртуальный интерфейс «Контейнер», который имеет что-то вроде «push (void * data) и pop (void * data)» (для записи , Я на самом деле не пытаюсь реализовать стек).
Однако мне не очень нравится void * на верхнем уровне, не говоря уже о том, что подпись будет меняться каждый раз, когда я хочу добавить ограничение на тип данных, с которыми может работать конкретный контейнер.
Подводя итог: у нас есть относительно сложные контейнеры, в которых есть разные способы извлечения элементов. Мы хотим иметь возможность изменять ограничения на элементы, которые могут входить в контейнеры. Элементы должны работать с несколькими типами контейнеров (при условии, что они соответствуют ограничениям этого конкретного контейнера).
Обновлено: я также должен упомянуть, что сами контейнеры должны быть полиморфными. Это моя основная причина, по которой я не хочу использовать шаблонный C++.
Итак, стоит ли мне отказаться от интерфейсов типа Java и перейти на шаблоны? Должен ли я использовать void * и статически приводить все? Или мне следует пойти с пустым определением класса «Элемент», которое ничего не объявляет, и использовать его в качестве моего класса верхнего уровня в иерархии «Элемент»?
Одна из причин, по которой мне нравится переполнение стека, заключается в том, что многие ответы содержат интересную информацию о других подходах, которые я даже не рассматривал. Так что заранее благодарим вас за ваши идеи и комментарии.
В частности, полиморфный контейнер - я забыл упомянуть об этом требовании. Насколько я знаю, это невозможно ... но тогда что я знаю.
> Мне нужно, чтобы сами контейнеры были полиморфными. Соответственно обновил свой ответ.
Зачем вам нужна сама структура данных контейнера, чтобы ее можно было отключить во время выполнения? Обычно вы выбираете структуру данных, которая имеет смысл с учетом операций, которые вам нужно выполнить с ней, и просто используете ее, а не переходите к интерфейсу с наименьшим общим знаменателем и выбираете во время выполнения.





Полиморфизм и шаблоны очень хорошо работают вместе, если вы их правильно используете.
В любом случае, я понимаю, что вы хотите хранить только один тип объектов в каждом экземпляре контейнера. Если да, используйте шаблоны. Это предотвратит ошибочное сохранение неправильного типа объекта.
Что касается интерфейсов контейнеров: в зависимости от вашего дизайна, возможно, вы тоже сможете сделать их шаблонными, и тогда у них будут такие методы, как void push(T* new_element). Подумайте, что вы будете знать об объекте, когда захотите добавить его в контейнер (неизвестного типа). Откуда в первую очередь возьмется объект? Функция, возвращающая void*? Вы знаете, что это будет сравнимо? По крайней мере, если все хранимые классы объектов определены в вашем коде, вы можете заставить их все унаследовать от общего предка, скажем, Storable, и использовать Storable* вместо void*.
Теперь, если вы видите, что объекты всегда будут добавляться в контейнер с помощью такого метода, как void push(Storable* new_element), тогда действительно не будет никакой дополнительной ценности в том, чтобы сделать контейнер шаблоном. Но тогда вы узнаете, что он должен хранить Storables.
Хотя это могло быть неправильно - я думаю, можно с уверенностью предположить, что да, каждый контейнер будет хранить элемент одного типа. Однако мне нужно, чтобы сами контейнеры были полиморфными. Вот что мне сложно в шаблонах.
«если вы их правильно используете» = крушение многих якобы хороших идей.
Самое простое - определить абстрактный базовый класс Container и создать подкласс для каждого типа элементов, которые вы, возможно, захотите сохранить. Затем вы можете использовать любой стандартный класс коллекции (std::vector, std::list и т. д.) Для хранения указателей на Container. Имейте в виду, что, поскольку вы будете хранить указатели, вам придется обрабатывать их выделение / освобождение.
Однако тот факт, что вам нужна одна коллекция для хранения объектов столь сильно разных типов, указывает на то, что что-то может быть не так с дизайном вашего приложения. Возможно, лучше еще раз вернуться к бизнес-логике, прежде чем реализовывать этот универсальный контейнер.
Вы можете использовать стандартный контейнер boost :: any, если вы храните в нем действительно произвольные данные.
Похоже, вы бы предпочли иметь что-то вроде boost :: ptr_container, где все, что может хранится в контейнере, должно происходить от некоторого базового типа, а сам контейнер может давать вам ссылку только на базовый тип.
Используя полиморфизм, вы в основном остаетесь с базовым классом для контейнера и производными классами для типов данных. Базовый класс / производные классы могут иметь столько виртуальных функций, сколько вам нужно, в обоих направлениях.
Конечно, это будет означать, что вам также нужно будет обернуть примитивные типы данных в производные классы. Если бы вы пересмотрели использование шаблонов в целом, я бы использовал их именно здесь. Создайте один производный класс из базы, которая является шаблоном, и используйте его для примитивных типов данных (и других, где вам не нужно больше функциональности, чем предусмотрено шаблоном).
Не забывайте, что вы можете облегчить себе жизнь с помощью typedef для каждого из шаблонных типов, особенно если вам позже понадобится превратить один из них в класс.
Во-первых, шаблоны и полиморфизм - это ортогональные концепции, и они хорошо сочетаются друг с другом. Далее, зачем вам нужна конкретная структура данных? Что насчет STL или структур данных Boost (в частности, указатель, содержащий) вам не подходит.
Учитывая ваш вопрос, похоже, что вы неправильно используете наследование в своей ситуации. Можно создать «ограничения» для содержимого ваших контейнеров, особенно если вы используете шаблоны. Эти ограничения могут выходить за рамки того, что вам могут дать ваш компилятор и компоновщик. На самом деле с наследованием такого рода вещи более неудобны, и ошибки, скорее всего, оставлены на время выполнения.
Сам контейнер имеет интересные свойства, не зависящие от хранимых в нем данных. И было бы неплохо, если бы он был полиморфным. Возможно, есть библиотека с открытым исходным кодом, которая близка к тому, что нам нужно, но я сомневаюсь, что она точно соответствует нашим требованиям.
Разве у вас нет корневого класса контейнера, который содержит элементы:
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
};
Вот о чем я думал. Единственная проблема в том, что Item по сути является пустым классом. Насколько я могу судить, я не могу накладывать никаких ограничений на то, что конкретно хранится в контейнере, поэтому на самом деле нет общего интерфейса, который я мог бы извлечь. Но, возможно, это не так уж и плохо.
Также .. Я не думаю, что в шаблоне могут быть виртуальные функции. Еще одна серьезная проблема;)
В шаблоне могут быть виртуальные функции.
Ах. Не знаю, почему я подумал, что ты не сможешь - я собираюсь кое-что проверить. Может быть, класс полиморфного шаблона будет делать все, что я хочу, если я смогу создать такую вещь.
Я только что протестировал свой пример, и AddItemToContainer вызывает виртуальный вызов List <Item> :: push.
Обратите внимание, что Item - это не класс, это параметр шаблона, поэтому любой класс, работающий с контейнером, будет работать.
Вы также можете проверить Библиотека проверки концепции Boost (BCCL), который предназначен для обеспечения ограничений на параметры шаблонов шаблонных классов, в данном случае ваших контейнеров.
И просто повторю то, что говорили другие, у меня никогда не было проблем со смешиванием полиморфизма и шаблонов, и я проделал с ними довольно сложные вещи.
Вы не могли отказаться от Java-подобных интерфейсов и также использовать шаблоны. Предложение Джоша общего базового шаблона. Контейнер, безусловно, позволит вам полиморфно передавать Контейнеры и их дочерние элементы, но, кроме того, вы, безусловно, можете реализовать интерфейсы как абстрактные классы, которые будут содержащимися элементами. Нет причин, по которым вы не могли создать абстрактный класс IComparable, как вы предложили, чтобы у вас могла быть полиморфная функция следующим образом:
class Whatever
{
void MyPolymorphicMethod(Container<IComparable*> &listOfComparables);
}
Теперь этот метод может принимать любой дочерний элемент Container, содержащий любой класс, реализующий IComparable, поэтому он будет чрезвычайно гибким.
что вы имеете в виду, что «полиморфизм и шаблоны несовместимы друг с другом»?