Альтернативы C++ указателям void * (это не шаблоны)

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

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


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

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

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

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

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

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

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

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

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

что вы имеете в виду, что «полиморфизм и шаблоны несовместимы друг с другом»?

David Nehme 03.10.2008 23:20

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

Voltaire 03.10.2008 23:24

> Мне нужно, чтобы сами контейнеры были полиморфными. Соответственно обновил свой ответ.

Lev 03.10.2008 23:37

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

Greg Rogers 04.10.2008 02:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
4
9 060
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

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

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

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

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

Voltaire 03.10.2008 23:20

«если вы их правильно используете» = крушение многих якобы хороших идей.

DarenW 03.10.2008 23:21

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

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

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

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

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

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

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

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

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

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

Voltaire 03.10.2008 23:27
Ответ принят как подходящий

Разве у вас нет корневого класса контейнера, который содержит элементы:

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 по сути является пустым классом. Насколько я могу судить, я не могу накладывать никаких ограничений на то, что конкретно хранится в контейнере, поэтому на самом деле нет общего интерфейса, который я мог бы извлечь. Но, возможно, это не так уж и плохо.

Voltaire 03.10.2008 23:33

Также .. Я не думаю, что в шаблоне могут быть виртуальные функции. Еще одна серьезная проблема;)

Voltaire 03.10.2008 23:34

В шаблоне могут быть виртуальные функции.

Lev 03.10.2008 23:39

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

Voltaire 03.10.2008 23:41

Я только что протестировал свой пример, и AddItemToContainer вызывает виртуальный вызов List <Item> :: push.

Eclipse 03.10.2008 23:42

Обратите внимание, что Item - это не класс, это параметр шаблона, поэтому любой класс, работающий с контейнером, будет работать.

KeithB 03.10.2008 23:45

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

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

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

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

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

Другие вопросы по теме