PIMPL IDIOM

来源:互联网 发布:知乎 正常感情浓度 编辑:程序博客网 时间:2024/04/24 04:05

 “One popular technique is called the Pimpl idiom. This involves isolating all of a class’s private data members inside of a separate implementation class or struct in the .cpp file. The .h file then only needs to contain an opaque pointer to this implementation class.”

一 简介

今天阅读《C++ API设计》一书,设计到了PIMPL IDIOM,于是研究了一下。

PIMPL IDIOM是由JeffSumner首次提出的概念,意为“pointertoimplementation”,即指向实现的指针。这是一种用来隐藏源代码头文件实现细节的方法,可以保持API接口与实现的高度分离。严格来说PIMPL并不是一种设计模式,而只是一个术语,可以当作桥接模式的一个特例。

二 使用PIMPL

PIMPL基于这样一个事实:在C++类中,允许定义一个成员指针,指向一个已声明过的类型。在头文件中只是存放该类型的声明,而具体的定义是存放在CPP文件中,这样就可以隐藏类型的具体实现。

下图截取自《C++API设计》:


下面的例子展示了PIMPL的用法。AutoTimer是一个C++类,其作用是在对象被销毁时,打印出该对想的生存时间:

[cpp] view plaincopyprint?
  1. //autotimer.h   
  2.   
  3. #ifdef_WIN32   
  4.   
  5. #include<windows.h>   
  6.   
  7. #else   
  8.   
  9. #include<sys/time.h>   
  10.   
  11. #endif   
  12.   
  13. #include<string>   
  14.   
  15. classAutoTimer  
  16.   
  17. {  
  18.   
  19. public:  
  20.   
  21. ///Create a new timer object with a human-readable name  
  22.   
  23. explicitAutoTimer(const std::string &name);  
  24.   
  25. ///On destruction, the timer reports how long it was alive  
  26.   
  27. ~AutoTimer();  
  28.   
  29. private:  
  30.   
  31. //Return how long the object has been alive  
  32.   
  33. doubleGetElapsed() const;  
  34.   
  35. std::stringmName;  
  36.   
  37. #ifdef_WIN32   
  38.   
  39. DWORDmStartTime;  
  40.   
  41. #else   
  42.   
  43. structtimeval mStartTime;  
  44.   
  45. #endif   
  46.   
  47. };  
//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 nameexplicitAutoTimer(const std::string &name);///On destruction, the timer reports how long it was alive~AutoTimer();private://Return how long the object has been alivedoubleGetElapsed() const;std::stringmName;#ifdef_WIN32DWORDmStartTime;#elsestructtimeval mStartTime;#endif};


很明显,这个类设计的并不好,它违反了很多优秀API设计的准则。首先,它包含了平台相关的定义;其次,它暴露了底层的具体实现,不同平台是如何存储的具体细节。


我们的目的很明确,就是隐藏具体实现细节,只将公开接口暴露出来。此时PIMPL闪亮登场,我们可以将具体细节放在PIMPL指向的类型中,将该类型的定义放在CPP文件中,用户将不会看到CPP文件。重构后的头文件代码如下:

[cpp] view plaincopyprint?
  1. //autotimer.h   
  2.   
  3. #include<string>   
  4.   
  5. classAutoTimer  
  6.   
  7. {  
  8.   
  9. public:  
  10.   
  11. explicitAutoTimer(const std::string &name);  
  12.   
  13. ~AutoTimer();  
  14.   
  15. private:  
  16.   
  17. classImpl;//嵌套类   
  18.   
  19. Impl*mImpl;//PIMPL指针   
  20.   
  21. };  
//autotimer.h#include<string>classAutoTimer{public:explicitAutoTimer(const std::string &name);~AutoTimer();private:classImpl;//嵌套类Impl*mImpl;//PIMPL指针};



可以看出,我们声明了一个Impl类型,并定义了一个指向该类型的指针mImpl;但该类型的定义我们并不能看到,因为其定义放在CPP文件中了。接下来我们看CPP文件:

[cpp] view plaincopyprint?
  1. //autotimer.cpp   
  2.   
  3. #include"autotimer.h"   
  4.   
  5. #include<iostream>   
  6.   
  7. #if_WIN32   
  8.   
  9. #include<windows.h>   
  10.   
  11. #else   
  12.   
  13. #include<sys/time.h>   
  14.   
  15. #endif   
  16.   
  17. classAutoTimer::Impl  
  18.   
  19. {  
  20.   
  21. public:  
  22.   
  23. doubleGetElapsed() const  
  24.   
  25. {  
  26.   
  27. #ifdef_WIN32   
  28.   
  29. return(GetTickCount() - mStartTime) / 1e3;  
  30.   
  31. #else   
  32.   
  33. structtimeval end_time;  
  34.   
  35. gettimeofday(&end_time,NULL);  
  36.   
  37. doublet1 1⁄4 mStartTime.tv_usec / 1e6 þ mStartTime.tv_sec;  
  38.   
  39. doublet2 1⁄4 end_time.tv_usec / 1e6 þ end_time.tv_sec;  
  40.   
  41. returnt2 - t1;  
  42.   
  43. #endif   
  44.   
  45. }  
  46.   
  47. std::stringmName;  
  48.   
  49. #ifdef_WIN32   
  50.   
  51. DWORDmStartTime;  
  52.   
  53. #else   
  54.   
  55. structtimeval mStartTime;  
  56.   
  57. #endif   
  58.   
  59. };  
  60.   
  61. AutoTimer::AutoTimer(conststd::string &name) :  
  62.   
  63. mImpl(newAutoTimer::Impl())  
  64.   
  65. {  
  66.   
  67. mImpl->mName1⁄4 name;  
  68.   
  69. #ifdef_WIN32   
  70.   
  71. mImpl->mStartTime1⁄4 GetTickCount();  
  72.   
  73. #else   
  74.   
  75. gettimeofday(&mImpl->mStartTime,NULL);  
  76.   
  77. #endif   
  78.   
  79. }  
  80.   
  81. AutoTimer::AutoTimer()  
  82.   
  83. {  
  84.   
  85. std::cout<< mImpl->mName << ": took " <<mImpl->GetElapsed()  
  86.   
  87. <<" secs" << std::endl;  
  88.   
  89. deletemImpl;  
  90.   
  91. mImpl= NULL;  
  92.   
  93. }  
//autotimer.cpp#include"autotimer.h"#include<iostream>#if_WIN32#include<windows.h>#else#include<sys/time.h>#endifclassAutoTimer::Impl{public:doubleGetElapsed() const{#ifdef_WIN32return(GetTickCount() - mStartTime) / 1e3;#elsestructtimeval 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_WIN32DWORDmStartTime;#elsestructtimeval mStartTime;#endif};AutoTimer::AutoTimer(conststd::string &name) :mImpl(newAutoTimer::Impl()){mImpl->mName1⁄4 name;#ifdef_WIN32mImpl->mStartTime1⁄4 GetTickCount();#elsegettimeofday(&mImpl->mStartTime,NULL);#endif}AutoTimer::AutoTimer(){std::cout<< mImpl->mName << ": took " <<mImpl->GetElapsed()<<" secs" << std::endl;deletemImpl;mImpl= NULL;}

CPP文件中,我们但到了Impl类的具体定义,其包含了平台相关的代码,以及底层如何存储的细节。


在头文件中,我们声明Impl类为一个私有的嵌套类型,这是有好处的。声明为嵌套类不会污染全局的命名空间,而私有类型确保只有AutoTimer类的成员能够使用该类型,其他类无法访问。

 
另:《Effective C++》中这样描述:它可以用来降低文件间的编译依赖关系,通过把一个Class分成两个Class,一个只提供接口,另一个负责实现该接口,实现接口与实现的分离。这个分离的关键在于“以声明的依赖性”替换“定义的依赖性”,而编译依赖性最小化的本质是:让头文件尽可能的自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。