C ++:避免​​在公共接口中使用特定于库的类型

时间:2013-06-05 18:40:13

标签: c++ friend

我目前正在使用DirectX进行渲染,在C ++中开发一个小型游戏引擎项目。引擎的呈现部分包含ModelTexture等类。因为我想保持(相对)简单切换到另一个渲染库(例如OpenGL)(并且因为我认为它只是很好的封装),我想保持这些类的公共接口完全没有任何对DirectX的引用类型,即我想避免提供ID3D11ShaderResourceView* GetTextureHandle();等公共功能。

但是,当Model等类需要Texture使用的内部纹理句柄来执行其任务时 - 例如实际渲染模型时,这就成了一个问题。为简单起见,让我们将DirectX替换为我们称之为Lib3D的任意3D渲染库。这是一个展示我面临的问题的例子:

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? */ );
    }
};

当然,我可以在GetTextureHandle中提供一个公共Texture函数,只返回指向mTexture的指针,但这意味着如果我更改底层渲染库,我会还必须更改该函数返回的类型,从而更改Texture的公共接口。更糟糕的是,也许新的库甚至没有相同的结构,这意味着我必须提供全新的功能!

我能想到的最佳解决方案是让Model成为Texture的朋友,以便它可以直接访问Texture的成员。然而,这似乎有点笨拙,因为我添加了更多使用Texture的类。我从来没有过多地使用友谊,所以我不确定这是否是一个可以接受的使用案例。

所以,我的问题是:

  • 声明Model的朋友是可接受的使用 友谊?这会是一个很好的解决方案吗?
  • 如果不是,你会怎样? 推荐?我是否需要重新设计我的课程结构 彻底?在那种情况下,任何提示?

PS:我意识到标题不是很具描述性,我为此道歉,但我真的不知道怎么说。

5 个答案:

答案 0 :(得分:0)

你需要抽象这个mo-fo。

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评论,也是个好主意。

答案 1 :(得分:0)

使用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
};

我认为这可能是一种解决该问题的简洁方法,并且可以轻松地切换出底层库。

答案 2 :(得分:0)

由于2个API是互斥的,因为您可能不需要在运行时在2之间切换,我认为您应该针对构建2个不同的可执行文件,每个底层API一个。 我的意思是使用:

#if OpenGL_implementation
  ...
#else // DirectX
  ...
#if

这可能是您正在寻找的性感解决方案,也可能不是。但我相信这是更简洁的解决方案。使用大量模板(相应的重型多态行为)可能会导致比#if解决方案更多的代码膨胀,并且它也会更慢地编译(运行)。 :)

换句话说,如果您能够在2个不同的可执行文件中获得所需的2个行为,则不应允许这对您的软件体系结构产生影响。只需构建2个性感的双胞胎软件解决方案,而不是1个胖子解决:)

答案 3 :(得分:0)

是否可接受使用友谊是值得商榷的。对于您使用的每个功能,甚至是好的功能,您都有可能在代码中形成反模式。因此,只需适度使用它,并对反模式保持谨慎。

虽然您可以使用友谊,但您也可以简单地使用继承,即IGLTexture:ITexture,并在需要访问实现细节的任何地方转换为适当的接口。例如,IGLTexture可以公开与opengl相关的所有内容。

甚至还有另一种可以使用的范例。 pimpl代表 私人实施。简而言之,而不是暴露实现细节 在类中,您只需在未公开指定实现的类中提供所有实现细节。我自己一直在使用这种方法,只有一点遗憾。

//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();
}

答案 4 :(得分:0)

根据我的经验,对于那些问题使用C ++继承通常会导致一个相当复杂且不可维护的项目。

基本上有两种解决方案:

  1. 抽象所有数据类型,使它们完全不依赖于渲染层。您将不得不从渲染层复制一些数据结构,但您只需要替换渲染代码。
  2. 选择一个便携式渲染层(OpenGL)并坚持下去。