4

我想为一个 CG 项目实现一个 Mesh 类,但遇到了一些问题。我想要做的是一个 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;
  };
};

出于灵活性考虑,每个 Mesh 实际上是通道的集合,如一个位置通道、一个普通通道等,它们描述了网格的某些方面。通道是 std::vector 的包装器,具有一些附加功能。

为了隐藏实现细节,每个 API(OpenGLMesh、DirectXMesh、CUDAMesh、...)都有一个派生类,用于处理特定于 API 的代码。通道(处理将通道数据加载到 API 的 OpenGLChannel 等)也是如此。Mesh 充当 Channel 对象的工厂。

但是问题来了:由于Channels是模板类,createChannel必须是模板方法,模板方法不能是虚的。我需要的是用于创建模板对象的工厂模式。有没有人对如何完成类似的事情有建议?

谢谢

4

4 回答 4

2

这是一个有趣的问题,但让我们先讨论编译器错误。

正如编译器所说,一个函数不能既是虚拟的又是模板的。要理解为什么,只需考虑实现:大多数时候,具有虚函数的对象都有一个虚表,其中存储了指向每个函数的指针。

然而,对于模板来说,功能与类型的组合一样多:那么虚拟表应该是什么样的呢?在编译时无法判断,并且您的类的内存布局包括虚拟表,必须在编译时确定。

现在谈谈你的问题。

最简单的解决方案是只为每种类型编写一个虚拟方法,当然它很快就会变得乏味,所以让我们假装你没有听说过。

如果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

于 2010-02-05T18:44:05.380 回答
0

如果您可以从 Mesh 中提取工厂(引入一些 ChannelFactory),那么您可以使用模板化工厂:

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

比你可以从 ChannelFactory 派生你的 OpenGLMesh,,,等等。

这种方法的唯一限制是您应该事先知道要在 OpenGLMesh 中使用哪些模板参数。

否则,您可能会对Boost.Any的工作原理感兴趣(boost::any保存任意类型的值)。

于 2010-02-05T12:54:09.600 回答
0

我会说你的整个设计都坏了。

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

这两行放在一起没有任何意义。不要试图修复编译器错误,想想你的概念。

首先,您将 CreateChannel 设为虚拟会员的具体原因是什么?

换句话说,C++ 是一种以允许各种复杂难以理解的设计而闻名的语言。而且你设法设计了一些甚至 C++ 都认为过于扭曲的东西。

于 2010-02-05T13:35:46.897 回答
0

频道是指“空间指数”吗?

如果你想隐藏实现细节,为什么要把它们放在你的网格中?

您希望网格具有相同的基本格式,可能在不同情况下以浮点数、双精度数或莫顿数为模板。应该改变的不是网格,而是它的加载方式。

于 2010-02-05T18:07:03.717 回答