c++:在公共接口中避免特定于库的类型

C++: avoiding library-specific types in public interface

本文关键字:于库 类型 接口 c++      更新时间:2023-10-16

我目前正在用c++做一个小游戏引擎项目,使用DirectX进行渲染。引擎的渲染部分由ModelTexture类组成。因为我想保持它(相对)简单切换到另一个渲染库(例如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)并坚持使用它。