一 简介
今天阅读《C++ API设计》一书,设计到了PIMPL IDIOM,于是研究了一下。
PIMPL IDIOM是由JeffSumner首次提出的概念,意为“pointertoimplementation”,即指向实现的指针。这是一种用来隐藏源代码头文件实现细节的方法,可以保持API接口与实现的高度分离。严格来说PIMPL并不是一种设计模式,而只是一个术语,可以当作桥接模式的一个特例。
二 使用PIMPL
PIMPL基于这样一个事实:在C++类中,允许定义一个成员指针,指向一个已声明过的类型。在头文件中只是存放该类型的声明,而具体的定义是存放在CPP文件中,这样就可以隐藏类型的具体实现。
下图截取自《C++API设计》:
下面的例子展示了PIMPL的用法。AutoTimer是一个C++类,其作用是在对象被销毁时,打印出该对想的生存时间:
//autotimer.h
#ifdef_WIN32
#include<windows.h>
#else
#include<sys/time.h>
#endif
#include<string>
classAutoTimer
{
public:
///Create a new timer object with a human-readable name
explicitAutoTimer(const std::string &name);
///On destruction, the timer reports how long it was alive
~AutoTimer();
private:
//Return how long the object has been alive
doubleGetElapsed() const;
std::stringmName;
#ifdef_WIN32
DWORDmStartTime;
#else
structtimeval mStartTime;
#endif
};
很明显,这个类设计的并不好,它违反了很多优秀API设计的准则。首先,它包含了平台相关的定义;其次,它暴露了底层的具体实现,不同平台是如何存储的具体细节。
我们的目的很明确,就是隐藏具体实现细节,只将公开接口暴露出来。此时PIMPL闪亮登场,我们可以将具体细节放在PIMPL指向的类型中,将该类型的定义放在CPP文件中,用户将不会看到CPP文件。重构后的头文件代码如下:
//autotimer.h
#include<string>
classAutoTimer
{
public:
explicitAutoTimer(const std::string &name);
~AutoTimer();
private:
classImpl;//嵌套类
Impl*mImpl;//PIMPL指针
};
可以看出,我们声明了一个Impl类型,并定义了一个指向该类型的指针mImpl;但该类型的定义我们并不能看到,因为其定义放在CPP文件中了。接下来我们看CPP文件:
//autotimer.cpp
#include"autotimer.h"
#include<iostream>
#if_WIN32
#include<windows.h>
#else
#include<sys/time.h>
#endif
classAutoTimer::Impl
{
public:
doubleGetElapsed() const
{
#ifdef_WIN32
return(GetTickCount() - mStartTime) / 1e3;
#else
structtimeval end_time;
gettimeofday(&end_time,NULL);
doublet1 1⁄4 mStartTime.tv_usec / 1e6 þ mStartTime.tv_sec;
doublet2 1⁄4 end_time.tv_usec / 1e6 þ end_time.tv_sec;
returnt2 - t1;
#endif
}
std::stringmName;
#ifdef_WIN32
DWORDmStartTime;
#else
structtimeval mStartTime;
#endif
};
AutoTimer::AutoTimer(conststd::string &name) :
mImpl(newAutoTimer::Impl())
{
mImpl->mName1⁄4 name;
#ifdef_WIN32
mImpl->mStartTime1⁄4 GetTickCount();
#else
gettimeofday(&mImpl->mStartTime,NULL);
#endif
}
AutoTimer::AutoTimer()
{
std::cout<< mImpl->mName << ": took " <<mImpl->GetElapsed()
<<" secs" << std::endl;
deletemImpl;
mImpl= NULL;
}
从CPP文件中,我们但到了Impl类的具体定义,其包含了平台相关的代码,以及底层如何存储的细节。
在头文件中,我们声明Impl类为一个私有的嵌套类型,这是有好处的。声明为嵌套类不会污染全局的命名空间,而私有类型确保只有AutoTimer类的成员能够使用该类型,其他类无法访问。