2012年3月26日 星期一

Effective C++ item 31: 將檔案間的編譯依存關係降至最低

C++ 相較於 Java, 可以直接在 stack 上配置物件, 而不是全部都是 pointer (reference), 因此, C++ 要求在 include class 時, 得知道該 class 的所有 member field, 才知道要配多大的空間。這帶來一個問題, 即使 header 檔改的是 private member field/method, 全部有引入該 header 的檔案都要重編, 專案變大後, 是頗為痛苦的事。

解決這問題有幾種作法, 基本精神就是架空 class, 讓它不會有更動 private member field/method 的機會。書上提出兩個作法:

  • 使用 handle class, 或稱 pimpl idiom (pimpl: pointer to implementation)
  • 使用 interface class (abstract base class)

以 class Person 為例, 第一個作法是在 Person 裡加上一個 private member: PersonImpl *pImpl, 然後所有方法都透過它執行, 例如:

std:string Person::name() const
{
    return pImpl->name();
}

interface class 則是全部方法都宣告為 virtual 並不提供實作 (別忘了提供 virtual destructor), 這樣 Person 不能初始化, 效果如同 Java 的 interface (不過有彈性可提供 method 和 field), 像這樣:

class Person
{
public:
    virtual ~Person();
    virtual std::string name() const = 0;
    ...
}

然後再寫個 class RealPerson 繼承 Person, 提供真正的實作。

於是, 不管用 handle class 或 interface class, 只要沒有加減 interface 的 method, 相依 Person 的程式都不需重編, 邏輯上也做到令用戶端程式相依於介面而非實作, 達到良好的封裝效果。當然, 相對於直接實作 class Person, 兩者的代價都是要多花一小點記憶體, 還有呼叫函式時多一點成本, 也失去 inline 最佳化的機會。但當程式愈寫愈大後, 這些代價都是值得的, 必要時再針對瓶頸最佳化較划算。

書上還提到其它的小細節, 像是將 header 拆成 X.h 和 Xfwd.h, 讓用戶端 include Xfwd.h, 然後各自再 include 會用到的實作定義。有些 class 有一長串 method, 各 method 用到各種不同 class (比方說用到 name() 的就要 include string 的 header), 但不是每個用戶端都會呼叫到這些 method, 自然也沒必要相依那些 method 傳入或傳回的 class 的 header。

沒有留言:

張貼留言

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...