c++:在公共接口中避免特定于库的类型
C++: avoiding library-specific types in public interface
我目前正在用c++做一个小游戏引擎项目,使用DirectX进行渲染。引擎的渲染部分由Model
和Texture
类组成。因为我想保持它(相对)简单切换到另一个渲染库(例如OpenGL)(因为我认为它只是很好的封装),我想保持这些类的公共接口完全没有任何对DirectX类型的引用,即我想避免提供公共函数,如ID3D11ShaderResourceView* GetTextureHandle();
。
这就成了一个问题,然而,当一个类,如Model
需要Texture
使用的内部纹理句柄来执行它的任务时——例如,当实际渲染模型时。为了简单起见,让我们将DirectX替换为一个任意的3D渲染库,我们称之为Lib3D。下面是一个示例,展示了我所面临的问题:
class Texture {
private:
Lib3DTexture mTexture;
public:
Texture(std::string pFileName)
: mTexture(pFileName)
{
}
};
class Model {
private:
Texture* mTexture;
Lib3DModel mModel;
public:
Model(std::string pFileName, Texture* pTexture)
: mTexture(pTexture), mModel(pFileName)
{
}
void Render()
{
mModel.RenderWithTexture( /* how do I get the Lib3DTexture member from Texture? */ );
}
};
当然,我可以在Texture
中提供一个公共GetTextureHandle
函数,它只返回一个指向mTexture
的指针,但这意味着如果我更改底层呈现库,我还必须更改该函数返回的类型,从而更改Texture
的公共接口。更糟糕的是,也许新的库甚至不是以相同的方式构建的,这意味着我必须提供全新的函数!
我能想到的最好的解决方案是使Model
成为Texture
的朋友,这样它就可以直接访问Texture
的成员。然而,当我添加更多使用Texture
的类时,这看起来有点笨拙。我从来没有使用过太多的友谊,所以我不确定这是否是一个可以接受的用法。
我的问题是:
- 是否可以将Model声明为Texture的朋友友谊?这是一个好的解决方案吗?
- 如果没有,你会怎么做推荐的吗?我需要重新设计我的类结构吗完全?这样的话,有什么建议吗?
PS:我意识到标题没有很好地描述,我为此道歉,但我真的不知道该怎么写
这是否是一种可以接受的友谊的使用是有争议的。对于您使用的每个特性,即使是好的特性,您都有可能在代码中形成反模式。因此,只要适度地使用它,并对反模式保持谨慎。
当你可以使用友谊时,你也可以简单地使用继承,即IGLTexture: ITexture,并在需要访问实现细节的地方转换到适当的接口。例如,IGLTexture可以暴露所有与opengl相关的内容。
甚至还有另一个范例可以使用。粉刺的意思是私人的实现。简而言之,而不是暴露实现细节在类中,您只需在未公开指定实现的类中提供所有实现细节。我自己也一直在用这种方法,很少后悔。
//header
class Texture
{
int width, height, depth;
struct Impl;
char reserved[32];
*Impl impl;
Texture();
...
};
//cpp
struct Texture::Impl
{
union
{
int myopenglhandle;
void* mydirectxpointer;
};
};
Texture::Texture()
{
impl = new (reserved) Impl();
}
你需要抽象这个动作
class TextureBase{
public:
virtual Pixels* getTexture() = 0;
virtual ~TextureBase(){}
};
class Lib3DTexture: public TextureBase {
private:
Lib3DTexture mTexture;
public:
Texture(std::string pFileName)
: mTexture(pFileName)
{
}
Pixels* getTexture(){ return mTexture.pixels(); }
};
class Renderable{
public:
virtual void render()const = 0;
virtual ~Renderable(){}
};
class ModelBase: public Renderable{
public:
virtual ModelData* getModelData() = 0;
virtual ~ModelBase(){}
};
class Lib3DModel : ModelBase{
private:
TextureBase* texture;
ModelBase* model;
public:
Lib3DModel(std::string pFileName, Texture* pTexture): mTexture(pTexture), mModel(pFileName){}
void render()const{
model.renderWithTexture( texture.getPixels() );
}
};
class World: public Renderable{
private:
std::vector< std::shared_ptr<Renderable> > visibleData;
public:
void render()const{
for_each(visiableData.begin(),visiableData.end(),std::mem_fun(Renderable::render));
}
};
你明白了,不保证它能编译,只是给你一个概念。也可以看看user2384250的评论,也是个好主意。
使用DirectX使Texture
成为具有默认模板参数的模板。
你可以这样写:
template<typename UnderlyingType = Lib3DTexture> class Texture {
private:
UnderlyingType mTexture;
public:
Texture(std::string pFileName)
: mTexture(pFileName)
{
}
UnderlyingType UnderlyingTexture(); //returns UnderlyingType, no matter which library you use
};
我认为这可能是解决这个问题的一种干净的方法,并且可以轻松地切换出底层库。
由于这两个API是互斥的,并且由于您可能不需要在运行时在两者之间切换,我认为您应该以构建两个不同的可执行文件为目标,每个用于底层API。我的意思是使用:
#if OpenGL_implementation
...
#else // DirectX
...
#if
这可能是也可能不是你正在寻找的性感的解决方案。但我相信这是一个更简洁的解决方案。大量使用模板(参见。严重的多态行为)可能会导致比#if解决方案更大的代码膨胀,而且它也会编译(例如:跑得慢一点。:)
换句话说,如果你可以在两个不同的可执行文件中拥有你想要的两种行为,你就不应该允许这对你的软件架构有影响。只需构建2个漂亮的软件解决方案,而不是1个臃肿的软件解决方案。:)
根据我的经验,使用c++继承来解决这类问题通常会导致一个非常复杂且不可维护的项目。
基本上有两种解决方案:
- 抽象所有数据类型,使它们完全不依赖于渲染层。您将不得不从呈现层复制一些数据结构,但您只需要替换呈现代码。选择一个便携式渲染层(OpenGL)并坚持使用它。
- 编译标准库类型
- 标准库类型的赋值运算符的引用限定符
- 如何泛化作用于不同类型的向量的函数?
- 为什么许多标准库类型在 C++20 中删除 operator!=?
- 如何动态加载和调用具有特定于库的类型作为函数参数的符号
- 依赖于依赖类型的非静态数据成员的非限定名称
- 清.如何生成具有不同库类型的Visual Studo解决方案?
- 从标准库类型特征继承
- 使用标准库类型作为QHASH或QSET中的键
- 是否允许使用标准库类型和内置类型过载操作员
- 如何使用类型专用化模板方法,该类型本身就是一个模板,其中只有返回类型依赖于模板类型
- 覆盖运算符<<适用于所有类型
- 为什么我们没有适用于所有类型的偏移量(具有虚拟继承的类型除外)?
- 类模板:为什么我不能将单一方法专用于空类型?
- 非库类型的无序key_type需要哈希<>专用化?
- 一个奇怪的C++错误,也许是相对于长长类型而言的
- C++开关只适用于积分类型的基本原理是什么
- 警告 C4180:应用于函数类型的限定符没有意义;忽视
- 如何将函数静态应用于非类型模板包的各个元素并对结果求和
- c++:在公共接口中避免特定于库的类型