我想为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;
};
};
为了灵活性,每个网格实际上是一个通道的集合,如一个位置通道,一个正常通道等,描述网格的某些方面。通道是std :: vector的包装器,带有一些附加功能。
要隐藏实现细节,每个API(OpenGLMesh,DirectXMesh,CUDAMesh,...)都有一个派生类,用于处理特定于API的代码。 Channels(OpenGLChannel等处理频道数据加载到API)也是如此。 Mesh充当Channel对象的工厂。
但问题是:由于Channels是模板类,createChannel必须是模板方法,而模板方法不能是虚拟的。我需要的是像工厂模式一样用于创建模板化对象。有没有人就如何实现类似的事情提出建议?
由于
答案 0 :(得分: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
答案 1 :(得分:0)
如果您可以从Mesh中提取工厂(引入一些ChannelFactory),那么您可以使用模板化工厂:
template <class T>
class ChannelFactory
{
public:
virtual TypedChannel<T>* createChannel() = 0;
};
你可以从ChannelFactory派生你的OpenGLMesh,无论如何。
此方法的唯一限制是您应事先知道要在OpenGLMesh中使用哪些模板参数。
否则您可能会对Boost.Any的工作原理感兴趣(boost::any
包含任意类型的值)。
答案 2 :(得分:0)
我说你的整个设计都破了。
virtual TypedChannel<T>* createChannel() = 0; // error: no virtual template functions
std::vector<Channel*> channels;
这两条线在一起没有任何意义。不要试图修复编译器错误,考虑一下你的概念。
首先,您对CreateChannel成为虚拟成员的理由究竟是什么?
换句话说,C ++是一种已知的语言,允许各种纠结的难以理解的设计。你设法设计了一些甚至C ++认为过于扭曲的东西。
答案 3 :(得分:0)
按频道,您的意思是'空间索引'吗?
如果您想隐藏实施细节,为什么要在网格物体中使用它们?
您希望网格是相同的基本格式,可能在不同情况下模板浮点数,双数或莫顿数。它不是应该改变的网格,只是它被加载的方式。