Итерация по коллекции различных типов в C++

Ситуация

У меня есть класс шаблона TIppImage<T> для изображения типа T. У меня есть одноэлементный класс CIppMemoryManager, который может хранить несколько изображений разного размера и типа.

class CIppMemoryManager
{
public:
  /// ... Singleton interface ... 

  template<class T> TIppImage<T>* GetImage(width, height);

private:
  CIppMemoryManager();
  ~CIppMemoryManager();

  std::map<IppDataType, void*> m_Containers;
};

IppDataType — это перечисление, значения которого соответствуют фактическим типам. Все управление осуществляется в классе шаблонов TIppImageContainer<T>. И вся специализация этого класса хранится в m_Containers как void*. Это не очень хорошо, но это, по крайней мере, просто.

При таком подходе я могу просто реализовать метод шаблона GetImage следующим образом:

template<class T> TIppImage<T>* CIppMemoryManager::GetImage(width, height)
{
  return reinterpret_cast<TIppImageContainer<T>*>(m_Containers[
    TIppTypeTraits<T>::ipp_data_type])->GetImage(width, height);
}

где я использую класс признаков TIppTypeTraits<T> для получения значения перечисления из данного типа.

Проблема

Я не могу просто реализовать нешаблонные методы, такие как конструктор. Мне нужно явно обрабатывать все возможные типы:

CIppMemoryManager::CIppMemoryManager()
{
  m_Containers[ipp8u] = new CIppImageContainer<Ipp8u>;
  m_Containers[ipp8s] = new CIppImageContainer<Ipp8s>;
  m_Containers[ipp16u] = new CIppImageContainer<Ipp16u>;
  m_Containers[ipp16s] = new CIppImageContainer<Ipp16s>;
  ...
}

Хуже того, для деструктора мне также нужно иметь дело с void*:

CIppMemoryManager::~CIppMemoryManager()
{
  delete reinterpret_cast<TIppImageContainer<Ipp8u>*>(m_Containers[ipp8u]);
  delete reinterpret_cast<TIppImageContainer<Ipp8s>*>(m_Containers[ipp8s]);
  delete reinterpret_cast<TIppImageContainer<Ipp16u>*>(m_Containers[ipp16u]);
  delete reinterpret_cast<TIppImageContainer<Ipp16s>*>(m_Containers[ipp16s]);
  ...
}

Итак, вопросы:

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

б) Есть ли лучший способ хранить коллекцию контейнеров - объектов разного типа? Когда они являются просто другой специализацией общего класса шаблонов, сами контейнеры довольно просты.


person Mikhail    schedule 28.07.2011    source источник


Ответы (4)


Что не так с полиморфным CIppImageContainer<T> (заставьте их всех использовать общий базовый класс) и интеллектуальным указателем?

Или какой-то boost::variant ?

person Alexandre C.    schedule 28.07.2011
comment
Похоже, это решит void* проблему уничтожения с помощью виртуального деструктора. Просто не подумал о нешаблонном предке шаблонных классов. Однако GetImage не может быть виртуальным, поскольку использует параметр шаблона. - person Mikhail; 28.07.2011

Я думаю, что вариант класса из библиотеки boost (boost::variant) может вам помочь. Вы можете использовать посетителей для выполнения соответствующего кода в зависимости от типа, сохраненного в варианте. std::vector<boost::variant<T0, T1,...>> может хранить список объектов разных типов.

Поскольку ваши объекты похожи, они могут иметь одинаковый размер в памяти, что хорошо, поскольку boost::variant хранилище основано на стеке (без выделения кучи - это быстрее).

person neodelphi    schedule 28.07.2011
comment
Как это поможет перебирать коллекцию разных типов? - person Mikhail; 29.07.2011
comment
Рассмотрим некоторый variant<int, float, double> и структуру my_visitor с оператором (), определенным для каждого типа, содержащегося в варианте. Если x является экземпляром variant<int, float, double>, то boost::apply_visitor(my_visitor(), x) вызовет соответствующий оператор() посетителя в зависимости от типа, сохраненного в варианте. Теперь, если у вас есть std::vector<variant<int, float, double>>, вы можете использовать apply_visitor для каждого элемента. - person neodelphi; 29.07.2011
comment
С трудом поверил, если честно. Информация о типе во время компиляции о каком-либо элементе вектора отсутствует, потому что это структура времени выполнения. Как он выведет правильную перегрузку?.. Или я должен просто пройти реализацию boost::variant? :) - person Mikhail; 31.07.2011
comment
Да, это обнаружение во время выполнения. Вариант внутри содержит индекс, указывающий, какой тип хранится. Вы можете рассматривать apply_visitor как переключатель (индекс), где каждый случай вызывает правильный метод перегрузки. - person neodelphi; 31.07.2011
comment
Спасибо за это! На самом деле я не собираюсь использовать его в этой конкретной задаче, но думаю, что его можно удобно использовать в каком-то другом случае. - person Mikhail; 01.08.2011

boost::mpl::for_each создан специально для этой работы. Определите вектор типов для работы, функтор или лямбда-выражение, чтобы что-то сделать, и все готово.

person thiton    schedule 28.07.2011

boost::variant является наиболее вероятным кандидатом, но иногда variantS становятся довольно большими, поскольку им требуется дополнительное хранилище, а также приходится иметь дело с выравниванием. Так что, возможно, boost::any имеет преимущества и в некоторых ситуациях:

std::vector<std::pair< Type, boost::any > > data;

Комфортно перебирать такой контейнер сложнее (boost::transform_iterator не может иметь более одного типа возвращаемого значения, так что это не сработает без некоторых трюков с шаблонами).

person pmr    schedule 28.07.2011