如何隐藏C++头文件中的实现

    嗯,先从一个问题说起,游戏引擎中的贴图管理模块该如何实现?我们可以分别想象一下这个模块在C和C++中的大体实现。注意,为了简化,下面的代码仅仅是示意一下而已。

一. C 中的实现

    C 通过头文件来暴露贴图模块的操作函数,texture.h 头文件代码如下:

#pragma once

//接口1:初始化贴图管理模块
void texture_init();

//接口2:加载一个贴图
void texture_load(int id);

//接口3:销毁一个贴图
void texture_unload(int id);

    第一感觉如何,是不是特别的干净利落!而至于在实现层面上,贴图到底是如何被管理的,这些信息在头文件中已经完全的透明化了,是的,实现完全的被隐藏了,我们用固定数组也好,用指针也罢,已经和模块用户完全的没关系了,无论我们如何修改实现,只要我们保证这个头文件不改变,那么依赖(#include)这个头文件的用户代码也就不需要重新编译。嗯,接下来看看C++中对象风格的实现。

二. C++对象风格的实现

    假设C++通过一个TextureManager类来管理贴图,TextureManager.h 头文件代码如下:

#pragma once

//Texture 类声明。
class Texture;

//TextureManager 类声明。
class TextureManager {
public:
    TextureManager() {}
    ~TextureManager() {}
public:
    //接口1:初始化贴图管理模块
    void init();
    
    //接口2:加载一个贴图
    void load(int id);

    //接口3:销毁一个贴图
    void unload(int id);
private:
    //请注意这里的纠结:这里已经暴露了我们是如何管理贴图的。
    //我们用一个vector来做了贴图池(texture pool)。
    std::vector<Texture> m_TexturesPool;
    
    //其他的一些私有实现层面的变量
    //。。。。。。
};

    看到上述的红色粗体警告了吗?明白问题的痛点在哪了吗?

    额,可能有人说,暴露就暴露了呗,你也忒小气了吧,人家Linux,GCC,Emacs都开源了,你这点Low逼代码还想要敝帚自珍? 额。。。。。。好吧,我承认刚开了个玩笑来试图幽默一下。

    这里真正的痛点是前面提过的【编译依赖】的问题。我们把贴图的管理机制给暴露了!是的,我们用了一个std::vector,但如果以后,我们发现用 std::list 来做贴图池会更适合一些,那怎么办?改呗,然后我们这一改,所有依赖(#include)这个头文件的代码,都需要重新编译了。好的,说到这里,我想我们应该已经明白了本文标题所说的隐藏C++头文件中的实现,是什么意思和目的了。好的,但依然需要总结一下。

三. 为什么要隐藏 C++ 头文件中的实现?

    这里就不啰嗦了,前面都剧透过了,就两点:

  • 为了让头文件看起来干脆利落,心情愉悦。
  • 为了尽可能的消除【编译依赖】。

    下面进入下一波内容。

四. 如何隐藏 C++ 头文件的实现?

    某个著名的计算机科学家说过一句话:“计算机科学领域内的几乎所有问题都可以通过引入一个额外的中间层来解决”。

    嗯,是的,这里我们也需要一个中间层,C++设计中的惯用手法:Impl 类

    嗯,通过引入一个额外的 Impl 类,我们可以做到让用户依赖的头文件中不再暴露实现上的细节。直接看如下的代码片段,引入了TextureManagerImpl 类:

//注意:TextureManagerImpl 类真正的实现了贴图管理逻辑。
class TextureManagerImpl;

//引入Impl类后,TextureManager的类声明。
class TextureManager {
public:
    TextureManager() {}
    ~TextureManager() {}
public:
    //接口1: 初始化贴图管理模块
    void init();

    //接口2:加载一个贴图
    void load(int id);

    //接口3:销毁一个贴图
    void unload(int id);
private:
    //请注意这里:我们已经隐藏了实现!!
      // 上述的三个公共接口都通过m_Impl指针来调用真正的实现,同时上一版这里的std::vector贴图池也转移到了TextureManagerImpl类中实现
    TextureManagerImpl *m_Impl;
};

   嗯,至此我们应该理解了 Impl 类的价值所在了,与 Impl 类结对出现的总会是 Wrapper 类。

  • 上述的 TextureManagerImpl 是 Impl 类,负责具体的功能实现,同时会对用户隐藏,用户不知道 Impl 类的存在。
  • 上述的 TextureManager       是 Wrapper 类,负责包装 Impl 类的接口,是模块对用户暴露的接口。

    全文结束。   

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。