出處:http://www.cnblogs.com/gnuhpc/

1.簡介

這個機制是Private Implementation的縮寫,我們常常聽到諸如“不要改動你的公有介面”這樣的建議,所以我們一般都會修改私有介面,但是這會導致包含該標頭檔案的所有原始檔都要重新編譯,這會是個麻煩事兒。Pimpl機制,顧名思義,將實現私有化,力圖使得標頭檔案對改變不透明。

2.機制分析

首先,我們先看看不使用這個機制的一個實現:

  1: // MyBase.h
  2: class MyBase {
  3: public:
  4:   int foo();
  5: };
  6:
  7: // MyDerived.h
  8: #include "MyBase.h"
  9: class MyDerived : public MyBase {
 10: public:
 11:   int bar();
 12: };

假設你現在希望在MyBase.h中加入一個新的private和protected成員函式,那麼MyDerived和所有包含MyBase.h的原始檔都需要重新編譯。在一個大工程中,這樣的修改可能導致重新編譯時間的激增。你可以使用Doxygen或者SciTools看看標頭檔案依賴。

一般來說,不在標頭檔案中包含標頭檔案是一個比較好的習慣,但是這也不能完全消除修改MyBase.h帶來的重新編譯代價。有沒有一個機制可以使得對私有介面做修改時我們可以減小重新編譯的代價。

在Pimpl機制中,我們使用前置宣告一個Impl類,並將這個類的一個指標例項放入主類中,如下:

  1: // MyClass.h
  2: class MyClassImpl;    // forward declaration
  3: class MyClass {
  4: public:
  5:   MyClass();
  6: ~MyClass();
  7:   int foo();
  8: private:
  9:   MyClassImpl *m_pImpl;
 10: };

現在,除非我們修改MyClass的公有介面,否則這個標頭檔案是不會被修改了。然後,我們用這個Impl類的實現來完成主類的細節實現,在主類的建構函式中,我們完成了實現類指標的例項化:

  1: // MyClass.cpp
  2: class MyClassImpl {
  3: public:
  4: int foo() {
  5:         return bar();
  6: }
  7: int bar() { return var++; }
  8:         int var;
  9: };
 10:
 11: MyClass::MyClass() : m_pImpl(new MyClassImpl){}
 12:
 13: MyClass::~MyClass()
 14: {
 15:     try {
 16:             delete m_pImpl;
 17:     }
 18:     catch (...) {}
 19: }
 20:
 21: int MyClass::foo(){ return m_pImpl->foo(); }

Pimpl機制其實這是橋接模式的一種變種。我們可以對實現類隨意的進行增刪和修改,而不會導致包含MyClass.h的原始碼重新編譯。當然,這樣做的時間開銷和空間開銷也是有的。

在實踐中,我們常常採用內部類來完成Pimpl機制:

  1: // header
  2: class fruit
  3: {
  4: public:
  5:
  6: private:
  7: class impl;
  8: impl* pimpl_;
  9: }
 10:
 11: // implementation
 12: class fruit::impl
 13: {
 14:
 15: };
 16:
 17: fruit::fruit()
 18: {
 19: pimpl_ = new impl();
 20: }