Guru of the Week 條款24:編譯級防火牆 (轉)

worldblog發表於2007-12-10
Guru of the Week 條款24:編譯級防火牆 (轉)[@more@]

[CAT*G Translation Project GotW#22-30: Draft]

GotW #24 Compilation s
著者:Herb Sutter
翻譯:CAT*G
[宣告]:本文內容取自網站上的Guru of the Week欄目,其著作權歸原著者本人所有。譯者CAT*G在未經原著者本人同意的情況下翻譯本文。本翻譯內容僅供自學和參考用,請所有閱讀過本文的人不要擅自轉載、傳播本翻譯內容;本翻譯內容的人請在閱讀瀏覽後,立即刪除其。譯者CAT*G對違反上述兩條原則的人不負任何責任。特此宣告。
Revision 1.0

Guru of the Week 條款24:編譯級

難度:6 / 10

(使用pimpl慣用法可以大大降低程式碼之間的相互依賴性,還可以減少的建立時間。但問題是,應該把那些東西放入pimpl裡呢?如何才能的使用它呢?)

[Problem]
[問題]

  在C++中,如果類定義中的任何部分被改變了(即使是私有成員),那麼這個類所有的使用者代媽都必須重新編譯。為了降低這種依賴性,使用的一種常見的技術就是利用一個不透明指標(opaque pointer)來隱藏一部分實現細節:

  class X {
  public:
  /* ... 公有成員 ... */
  protected:
  /* ... 保護成員?... */
  private:
  /* ... 私有成員?... */
  class XImpl* pimpl_;  // 指向一個被前置宣告瞭的(forward-declared)類
  // 之不透明指標
  };

[Questions]
[提問]

1.那些部分應該放入Ximpl?有四種常見的原則,它們是:

    - 將全部私有資料(但不是)放入Ximpl;
    - 將全部私有成員(譯註:即包括函式)放入Ximpl;
    - 將全部私有成員和保護成員放入Ximpl;
    - 使Ximpl完全成為原來的X,將X編寫為一個完全由簡單的前置函式(forwarding functions)(一個控制程式碼/本體的變體)組成的公共介面。

它們各有什麼優缺點?你如何從中選擇合適的?

2.Ximpl需要一個指向X物件的"反向指標(back pointer)"嗎?

[Solution]
[解答]

首先看兩個定義:

  可見類(visible class):客戶代嗎所見並操縱的類(在這裡即是X)。
  pimpl:可見類中隱藏在一個透明指標(也稱為pimpl_)下的類實現(在這裡即是XImpl)。

  在C++中,如果類定義中的任何部分被改變了(即使是私有成員),那麼這個類所有的使用者代媽都必須重新編譯。為了降低這種依賴性,使用的一種常見的技術就是利用一個不透明指標(opaque pointer)來隱藏一部分實現細節:

  這是"控制程式碼/本體"慣用法(handle/body idiom)的一種變體。如Coplien[注1]所記載,這種方法主要用於在共享程式碼的情況下進行引用計數(reference counting)。

  正如Lakos[注2]所指出的那樣,"控制程式碼/本體"慣用法(表現為我所稱為的"pimpl慣用法"之形式;這樣的叫法緣自給其特意取的、易發音的"pimpl_"指標[注3])對於打破編譯期的依賴性也是非常有用的。本條款的解答集中討論這種用法,其中有些討論總的來講並不適從於"控制程式碼/本體"慣用法。

  使用這個慣用法的主要代價是:

  1.每一個構造操作都須分配。自己定製的分配器(custom allocator)或許可以緩解記憶體的額外消耗,但這還不是涉及到更多的工作。

  2.每一個隱藏成員都需要一個額外的間接層來予以對其訪問。(如果被訪問的隱藏成員本身又使用到了一個"反向指標(back pointer)"來可見類中的函式,那麼就會有雙重的間接性。)

    1.那些部分應該放入Ximpl?有四種常見的原則,它們是:
      - 將全部私有資料(但不是函式)放入Ximpl;

  這是個不錯的開端,因為現在我們得以對任何只用作資料成員的類進行前置宣告(forward-declare)(而不是使用#include語句來包含類的真正宣告--這會使客戶程式碼對其形成依賴)。當然,我們通常可以做得更好。

      - 將全部私有成員(譯註:即包括函式)放入Ximpl;

  這(幾乎)是我平常的用法。不管怎麼說,在C++中,"客戶程式碼不應該也並不關心這些部分"就意味著"私有(private)",而私有的東西則最好藏起來(在一些擁有更"自由寬大"之法律的北歐國家裡的情況除外)。

  對此有兩條警告,其中的第一個也就是我在上一段中加上"almost"的原因:

  1.即使虛擬函式是私有的,你也不能把虛擬成員函式隱藏在pimpl類中。如果想要虛擬函式覆寫基類中的同名虛擬函式,那麼該虛擬函式就必須出現在真正的派生類中。如果虛擬函式不是繼承而來的,那麼為了讓之後層級的派生類能夠覆寫它,其還是必須出現在可見類中。

  2.如果pimpl中的函式要使用其它函式,其可能需要一個指向可見物件的"反向指標(back pointer)"--這又增加了一層間接性。這個反向指標通常被約定俗成的稱為self_。

       - 將全部私有成員和保護成員放入Ximpl;

  如此更進一步的做法其實是錯誤的。保護成員(protected members)絕不應該被放進pimpl,因為這樣做等於就是對其棄之不用。無論如何,保護成員(protected members)正是為了讓派生類看到並使用而存在的,因此如果派生類無法看到或使用它們的話,它們也就基本上全無用處了。


      - 使Ximpl完全成為原來的X,將X編寫為一個完全由簡單的前置函式(forwarding functions)(一個控制程式碼/本體的變體)組成的公共介面。


  這隻在少數幾個有限的情況下有用,其好處是可以避免使用反向指標,因為所有的服務全部都在pimpl類之中提供了。其主要的缺點是,這樣做一般會使得可見類對於繼承而言全無用處,無論是作為基類還是派生類。

      2.Ximpl需要一個指向X物件的"反向指標(back pointer)"嗎?

  很不幸,回答通常為"是的"。無論如何,我們會把每一個物件分裂成兩部分--只因為我們要隱藏其中一部分。

  當可見類中的函式被呼叫的時候,經常需要使用隱藏部分(譯註:即pimpl部分)中的一些函式和資料,以便完成是呼叫著的請求。這挺好,也很合理。然而可能不太明顯的情況是:在pimpl中的函式也經常必須呼叫可見類中的函式--通常是因為需要呼叫的函式是公有成員或虛擬函式。

 

[注1]:James O. Coplien. Advanced C++ Programming Styles and Idioms (Addison-Wesley, 1992).

[注2]:J. Lakos. Large-Scale C++ Software Design (Addison-Wesley, 1996).

[注3]:我以前經常將其寫作impl_。與其等價的pimpl_寫法其實是有我的朋友和同事Jeff Sumner提出的;Jeff Sumner同我一樣對用於指標變數的匈牙利式"p"字首(譯註:即用於命名變數的匈牙利表示法;在該表示法中,指標變數名以"p"開頭,意即"pointer")頗為傾心,另外其還對恐怖的雙關語有著獨到的敏感(譯註:pimpl發音與單詞"pimple(意即痤瘡、丘疹、膿皰、疙瘩、粉刺)"相同)。


(完)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991108/,如需轉載,請註明出處,否則將追究法律責任。

相關文章