一 简介
今天阅读《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类的成员能够使用该类型,其他类无法访问。