C++ 使用抽象工厂和模板元编程的可扩展体系结构
我目前正在写我的硕士论文,似乎我找不到一个令人满意的解决以下问题的方法。这里的想法是设计一个从底层API(如DirectX11和OpenGL4)提取的小型库。在同一个应用程序中,不需要两个或多个API共存,因此理论上我可以编写一组预处理器指令来区分它们,但是这会破坏我的代码,当然,它根本不可扩展 抽象工厂似乎非常方便,但是我似乎找不到一种方法使它与模板一起工作 让我们开始 我有一个抽象类C++ 使用抽象工厂和模板元编程的可扩展体系结构,c++,oop,architecture,C++,Oop,Architecture,我目前正在写我的硕士论文,似乎我找不到一个令人满意的解决以下问题的方法。这里的想法是设计一个从底层API(如DirectX11和OpenGL4)提取的小型库。在同一个应用程序中,不需要两个或多个API共存,因此理论上我可以编写一组预处理器指令来区分它们,但是这会破坏我的代码,当然,它根本不可扩展 抽象工厂似乎非常方便,但是我似乎找不到一种方法使它与模板一起工作 让我们开始 我有一个抽象类工厂,其目的是实例化应用程序工作所需的对象,例如资源和上下文。前者用于在运行时加载资源,而后者用于渲染三维场景
工厂
,其目的是实例化应用程序工作所需的对象,例如资源
和上下文
。前者用于在运行时加载资源,而后者用于渲染三维场景。
资源
和上下文
都是抽象的,因为它们的实现依赖于底层API
class Factory{
public:
virtual Resources & GetResources() = 0;
virtual Context & GetContext() = 0;
}
Resources
类将加载所需的资源,并返回类型为Texture2D
和Mesh
的对象。同样,这些类是抽象的,因为它们依赖于特定的API
假设我正在使用DirectX11和OpenGL4.5。对于这些API中的每一个,我都派生了上面的类,分别是DX11Factory
,DX11Resources
,DX11Context
,DX11Texture2D
,DX11Mesh
等等。他们扩展的类是非常明显的。很公平
设计类资源
接口的简单方法如下:
class Resources{
public:
Texture2D LoadTexture(const wstring & path) = 0;
Mesh LoadMesh(const wstring & path) = 0;
}
class Texture2D{...}
class Resources{
public:
template<typename TResource>
virtual TResource Load(const wstring & path) = 0; // :(
}
namespace dx11{
class DX11Texture2D: public Texture2D{...}
class DX11Texture2DLoader{...}
template<typename TResource> struct resource_traits;
template<> struct resource_traits<Texture2D>{
using type = DX11Texture2D;
using loader = DX11Texture2DLoader; //Functor type
}
class DX11Resources{
public:
template<typename TResource>
virtual TResource Load(const wstring & path){
return typename resource_traits<TResource>::loader()( path );
}
}
}
类DX11Resource
将实现上述方法,一切都将正常工作。。。除了如果我想在将来支持一种新的资源类型,比如TextureCube
(从软件工程师的角度来看,我现在肯定会支持),我必须在库用户实际使用的界面中声明一种新方法TextureCube LoadTextureCube(…)
,即Resources
。这意味着我必须在每个派生类中实现该方法(开-闭原则FTW!)
我解决这个问题的第一个想法是:
class Resources{
public:
Texture2D LoadTexture(const wstring & path) = 0;
Mesh LoadMesh(const wstring & path) = 0;
}
class Texture2D{...}
class Resources{
public:
template<typename TResource>
virtual TResource Load(const wstring & path) = 0; // :(
}
namespace dx11{
class DX11Texture2D: public Texture2D{...}
class DX11Texture2DLoader{...}
template<typename TResource> struct resource_traits;
template<> struct resource_traits<Texture2D>{
using type = DX11Texture2D;
using loader = DX11Texture2DLoader; //Functor type
}
class DX11Resources{
public:
template<typename TResource>
virtual TResource Load(const wstring & path){
return typename resource_traits<TResource>::loader()( path );
}
}
}
因此,基本上编译器将无法执行正确的替换,它将向用户甚至不知道的类指出错误。
)
我曾考虑过其他解决方案,但没有一个能满足我的需求:
1.
我可以用这个:
template <typename TDerived>
class Resources{
public:
template <typename TResource>
TResource Load(const wstring & path){
return typename TDerived::resource_traits<TResource>::loader()( path );
}
}
在派生类中,我必须实现上面的纯虚拟方法,然后,使用if-then-else级联,我可以实例化我需要的资源,或者在特定API不支持时返回null ptr。这肯定会起作用,但它是丑陋的,当然,每当我想要支持一个新的资源类型时(但至少只有一个类),它就迫使我重写实现
3.访客
利用访客模式。这种方法实际上对我一点帮助都没有,但我把它放在这里以防万一(每当我看到一个无休止的if-then-else与集成的downcast级联时,我总是想到访问者,就像前面的一点:)
模板资源特性;
模板资源{
使用visitable=Texture2DVisitable;
}
结构纹理2dvisitable{
Texture2D运算符()(常量wstring和path、加载器和访问者){
return visitor.Load(路径,*this);
}
}
模板
TResource资源::加载(路径){
返回typename资源特性::visitable()(路径,*this);
}
使用这种方法,Resources
现在必须为它可以加载的每个资源声明一个纯虚拟方法,比如Texture2D Resources::load(path,Texture2DVisitable&)=0
。所以,再一次,在新资源的情况下,我必须相应地更新整个层次结构。。。在这一点上,我将在开始时使用平凡的解决方案
4.其他人?
我错过什么了吗?我应该选择什么方法?我觉得我总是在把事情复杂化
提前谢谢你,很抱歉我写得很差
ps:首先摆脱Resource类不是一个选项,因为它的真正目的是防止反复加载相同的资源。这基本上是一个巨大的flyweight。这个问题实际上可以归结为整个“虚拟函数模板”问题。基本上,解决方案(不管是什么)必须获取编译时信息(例如,模板参数),将其转换为运行时信息(例如,值、类型id、哈希代码、函数指针等),通过运行时调度(虚拟调用),然后将运行时信息转换回编译时信息(例如,要执行哪段代码)。通过理解这一点,您将认识到最直接的解决方案是使用“RTTI”解决方案或其变体 正如您所指出的,该解决方案唯一真正的问题是它“丑陋”。我同意它有点丑陋,除此之外,它是一个不错的解决方案,尤其是添加新的受支持类型时所需的修改仅局限于实现(cpp文件)与您正在添加该支持的类关联(您真的不希望有比这更好的东西) 至于丑陋,好吧,这是你可以通过一些技巧来改进的东西,但是它总是会有一些丑陋,尤其是
静态\u cast
,它不能被删除,因为你需要一种从运行时调度返回静态类型结果的方法e> 标准::类型索引:
// Resources.h:
class Resources {
public:
template <typename TResource>
TResource Load(const wstring & path){
return *static_cast<TResource *>(Load(path, std::type_index(typeid(TResource))));
}
protected:
virtual void* Load(const wstring & path, std::type_index t_id) = 0;
}
// DX11Resources.h:
class DX11Resources : public Resources {
protected:
void* Load(const wstring & path, std::type_index t_id);
};
// DX11Resources.cpp:
template <typename TResource>
void* DX11Res_Load(DX11Resources& res, const wstring & path) { };
template <>
void* DX11Res_Load<Texture2D>(DX11Resources& res, const wstring & path) {
// code to load Texture2D
};
// .. so on for other things..
void* DX11Resources::Load(const wstring & path, std::type_index t_id) {
typedef void* (*p_load_func)(DX11Resources&, const wstring&);
typedef std::unordered_map<std::type_index, p_load_func> MapType;
#define DX11RES_SUPPORT_LOADER(TYPENAME) MapType::value_type(std::type_index(typeid(TYPENAME)), DX11Res_Load<TYPENAME>)
static MapType func_map = {
DX11RES_SUPPORT_LOADER(Texture2D),
DX11RES_SUPPORT_LOADER(Texture3D),
DX11RES_SUPPORT_LOADER(TextureCube),
//...
};
#undef DX11RES_SUPPORT_LOADER
auto it = func_map.find(t_id);
if(it == func_map.end())
return nullptr; // or throw exception, whatever you prefer.
return it->second(*this, path);
};
//Resources.h:
班级资源{
公众:
模板
TResource负载(常数wstring和路径){
return*static_cast(加载)路径,std::type_索引(typei
template <typename TResource> resource_traits;
template<> resource_traits<Texture2D>{
using visitable = Texture2DVisitable;
}
struct Texture2DVisitable{
Texture2D operator()(const wstring & path, Loader & visitor){
return visitor.Load(path, *this);
}
}
template<typename TResource>
TResource Resources::Load(path){
return typename resource_traits<TResource>::visitable()(path, *this);
}
// Resources.h:
class Resources {
public:
template <typename TResource>
TResource Load(const wstring & path){
return *static_cast<TResource *>(Load(path, std::type_index(typeid(TResource))));
}
protected:
virtual void* Load(const wstring & path, std::type_index t_id) = 0;
}
// DX11Resources.h:
class DX11Resources : public Resources {
protected:
void* Load(const wstring & path, std::type_index t_id);
};
// DX11Resources.cpp:
template <typename TResource>
void* DX11Res_Load(DX11Resources& res, const wstring & path) { };
template <>
void* DX11Res_Load<Texture2D>(DX11Resources& res, const wstring & path) {
// code to load Texture2D
};
// .. so on for other things..
void* DX11Resources::Load(const wstring & path, std::type_index t_id) {
typedef void* (*p_load_func)(DX11Resources&, const wstring&);
typedef std::unordered_map<std::type_index, p_load_func> MapType;
#define DX11RES_SUPPORT_LOADER(TYPENAME) MapType::value_type(std::type_index(typeid(TYPENAME)), DX11Res_Load<TYPENAME>)
static MapType func_map = {
DX11RES_SUPPORT_LOADER(Texture2D),
DX11RES_SUPPORT_LOADER(Texture3D),
DX11RES_SUPPORT_LOADER(TextureCube),
//...
};
#undef DX11RES_SUPPORT_LOADER
auto it = func_map.find(t_id);
if(it == func_map.end())
return nullptr; // or throw exception, whatever you prefer.
return it->second(*this, path);
};