Альтернатива Factory Pattern при создании шаблонных объектов - C ++ - PullRequest
4 голосов
/ 05 февраля 2010

Я хочу реализовать класс Mesh для CG-проекта, но столкнулся с некоторыми проблемами. То, что я хочу сделать, - это класс Mesh, который скрывает детали реализации (такие как загрузка в определенный API: OpenGL, DirectX, CUDA, ...) от пользователя. Кроме того, поскольку класс Mesh будет использоваться в исследовательских проектах, этот класс Mesh должен быть очень гибким.

class Channel {
  virtual loadToAPI() = 0;      
}

template <class T>
class TypedChannel : public Channel {

  std::vector<T> data;
};

template <class T>
class OpenGLChannel : public TypedChannel<T> {

  loadToAPI(); // implementation
};

class Mesh {

  template<class T>
  virtual TypedChannel<T>* createChannel() = 0; // error: no virtual template functions

  std::vector<Channel*> channels;
};

class OpenGLMesh {

  template<class T>
  TypedChannel<T>* createChannel()
  {
    TypedChannel<T>* newChannel = new OpenGLChannel<T>;
    channels.push_back(newChannel);
    return newChannel;
  };
};

Для гибкости каждая сетка на самом деле представляет собой набор каналов, таких как один позиционный канал, нормальный канал и т. Д., Которые описывают некоторые аспекты сетки. Канал - это обертка вокруг std :: vector с некоторыми дополнительными функциями.

Чтобы скрыть подробности реализации, для каждого API (OpenGLMesh, DirectXMesh, CUDAMesh, ...) есть производный класс, который обрабатывает специфичный для API код. То же самое касается каналов (OpenGLChannel и т. Д., Которые обрабатывают загрузку данных канала в API). Сетка действует как фабрика для объектов Канала.

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

Спасибо

Ответы [ 4 ]

2 голосов
/ 05 февраля 2010

Это интересная проблема, но давайте сначала обсудим ошибку компилятора.

Как сказал компилятор, функция не может быть одновременно виртуальной и шаблонной. Чтобы понять почему, просто подумайте о реализации: в большинстве случаев объекты с виртуальными функциями имеют виртуальную таблицу, в которой хранится указатель на каждую функцию.

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

Теперь к вашей проблеме.

Самое простое решение - написать один виртуальный метод для каждого типа, конечно, это может скоро стать утомительным, поэтому давайте представим, что вы этого не слышали.

Если Mesh не должен знать о различных типах, то, конечно, вам не нужно, чтобы эта функция была virtual, потому что кто бы знал, учитывая экземпляр Mesh, с каким типом вызывается функция?

Mesh* mesh = ...;
mesh.createChannel<int>(); // has it been defined for that `Mesh` ??

С другой стороны, я предполагаю, что OpenGLMesh точно знает, какой тип TypedChannel ему понадобится. Если это так, мы могли бы использовать очень простой трюк.

struct ChannelFactory
{
  virtual ~ChannelFactory() {}
  virtual Channel* createChannel() = 0;
};

template <class T>
struct TypedChannelFactory: ChannelFactory
{
};

А потом:

class Mesh
{
public:
  template <class T>
  Channel* addChannel()
  {
    factories_type::const_iterator it = mFactories.find(typeid(T).name());
    assert(it != mFactories.end() && "Ooops!!!" && typeid(T).name());

    Channel* channel = it->second->createChannel();
    mChannels.push_back(channel);
    return channel;
  } // addChannel

protected:
  template <class T>
  void registerChannelFactory(TypedChannelFactory<T>* factory)
  {
    mFactories.insert(std::make_pair(typeid(T).name(), factory));
  } // registerChannelFactory

private:
  typedef std::map < const char*, ChannelFactory* const > factories_type;
  factories_type mFactories;

  std::vector<Channel*> mChannels;
}; // class Mesh

Это демонстрирует довольно мощную идиому, известную как type erasure. Вы, вероятно, использовали его еще до того, как узнали его имя:)

Теперь вы можете определить OpenGLMesh как:

template <class T>
struct OpenGLChannelFactory: TypedChannelFactory<T>
{
  virtual Channel* createChannel() { return new OpenGLChannel<T>(); }
};

OpenGLMesh::OpenGLMesh()
{
  this->registerChannelFactory(new OpenGLChannelFactory<int>());
  this->registerChannelFactory(new OpenGLChannelFactory<float>());
}

И вы будете использовать его как:

OpenGLMesh openGLMesh;
Mesh& mesh = openGLMesh;

mesh.addChannel<int>();    // fine
mesh.addChannel<float>();  // fine

mesh.addChannel<char>();   // ERROR: fire the assert... (or throw, or do nothing...)

Надеюсь, я понял, что вам нужно: p

0 голосов
/ 05 февраля 2010

Я бы сказал, что весь ваш дизайн сломан.

  virtual TypedChannel<T>* createChannel() = 0; // error: no virtual template functions
  std::vector<Channel*> channels;

Эти две строки просто не имеют никакого смысла вместе. Не пытайтесь исправить ошибку компилятора, продумайте ваши концепции.

Для начала, что именно вы думаете о том, чтобы сделать CreateChannel виртуальным членом?

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

0 голосов
/ 05 февраля 2010

Под каналом вы подразумеваете «пространственный индекс»?

Если вы хотите скрыть детали реализации, почему они у вас в сетке?

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

0 голосов
/ 05 февраля 2010

Если вы можете извлечь фабрику из Mesh (представив некоторую ChannelFactory), то вы можете использовать шаблонизированную фабрику:

template <class T>
class ChannelFactory
{
public:
   virtual TypedChannel<T>* createChannel() = 0;
};

Чем вы могли бы извлечь свой OpenGLMesh из ChannelFactory, что угодно.

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

В противном случае вам может быть интересно, как работает Boost.Any (boost::any содержит значения произвольного типа).

...