《C++ API设计》读书笔记之 《Pimpl惯用法》

Pimpl是“pointer to implementation”的缩写, 该技巧可以避免在头文件中暴露私有细节。Pimpl利用了c++的一个特点,即可以将类的数据成员定义为指向某个已经声明过的类型的指针, 这里的类型仅仅作为名字引入, 并没有被完整地定义。

例如“自动定时器”的API, 它是一个具名对象,当对象被销毁时打印出其生存时间。

  • AutoTimer.h 如下:
#ifdef _WIN32 
#include 
#else
#include 
#endif

#include 

class AutoTimer
{
public:
    explicit AutoTimer(const std::string name);
    ~AutoTimer();
private:
    double getElapsed() const;
    std::string mName;

#ifdef _WIN32 
    DWORD mStartTime;
#else
    struct timeval mStartTime;
#endif
    
};

从上面的h文件可知,该API暴露了定时器在不同平台上存储的底层细节,利用Pimpl特性, 可轻易的隐藏掉这些底层实现的细节, 利用Pimpl特效后新的h文件如下:

#include 

class AutoTimer{
public:
    explicit AutoTimer(const std::string& name);
    ~AutoTimer();
private:
    class Impl;
    Impl* mImpl;
};

新API更简洁, 没有任何平台相关的预处理指令, 他人也不能通过头文件了解类的任何私有成员。

现在AutoTimer的功能实现都放在内嵌类Impl中了, 具体的AutoTimer的cpp文件如下:


#include "AutoTimer.h"

#ifdef _WIN32
#include 
#else

#include 
#include 

#endif

class AutoTimer::Impl
{
public:
    double getElapsed() const
    {
#ifdef _WIN32
        return (GetTickCount() - mStartTime) / 1e3;
#else
        struct timeval end_time;
        gettimeofday(&end_time, NULL);
        double t1 = mStartTime.tv_usec / 1e6 + mStartTime.tv_sec;
        double t2 = end_time.tv_usec / 1e6 + end_time.tv_sec;
        return t2 - t1;
#endif
    }

    std::string mName;
#ifdef _WIN32
    DWORD mStartTime;
#else
    struct timeval mStartTime;
#endif

};

AutoTimer::AutoTimer(const std::string &name)
: mImpl(new AutoTimer::Impl())
{
    mImpl->mName = name;
#ifdef _WIN32
    mImpl->mStartTime = GetTickCount();
#else
    gettimeofday(&mImpl->mStartTime, NULL);
#endif
}

AutoTimer::~AutoTimer()
{
    std::cout << mImpl->mName << ":took " << mImpl->getElapsed() << " secs" << std::endl;
    delete mImpl;
    mImpl = NULL;
}

将Impl类声明为AutoTimer类的私有内嵌类, 可以避免与该实现相关的符号污染全局命名空间。

  • Pimpl的优点:
  1. 信息隐藏, 将具体实现放在cpp文件中, h文件只暴露公有接口;
  2. 降低耦合;
  3. 加速编译;
  4. 更好的二进制兼容性,二进制兼容性指的是在升级库文件的时候(windows下的dll, uinx下的so),不必重新编译使用这个库的可执行文件或使用这个库的其他库文件,程序的功能不被破坏;
  5. 惰性分配;
  • 缺点
  1. 添加了一层封装, 可能进入性能问题;
  2. 代码可读性降低;

你可能感兴趣的:(《C++ API设计》读书笔记之 《Pimpl惯用法》)