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。

2012年3月25日 星期日

vim 快速開啟 .c / .cpp / .h 的相對檔案

搜尋這個需求的話, 會看到網路上一個廣為流傳的版本:

map <f4> :p,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp,

開 .cpp 後按 F4 就可以開啟同目錄下的 .h; 反之亦然。只要一行就搞定, 沒有 if-else, 頗為神奇的。

但是有時候我開 .cc 或 .c 時, 就沒辦法叫出 .h 檔, 所以剛才花了點時間研究一下, 才搞懂它怎麼做的。

修改後的版本如下, 這個版本在 .cpp / .c / .cc / .C 檔案裡按 F4 會開 .h; 在 .h 裡開 F4 會開 .cpp:

map <F4> :vs %:s#\.cpp$#.XY_CPP_XY#:s#\.h$#.cpp#:s#.XY_CPP_XY#.h#:s#\.cc$#.h#:s#\.[cC]$#.h#<CR>

% 表示目前的檔名, 在 vim 打 :help :%s, 可看到 :%s 的用處, 它是用來取代檔名的指令, 功能同 s/.../.../, 特別的是, 它可以重覆使用。

原作者的巧思在於, 先將 .h 轉成一個大概不會出現的字串 (.X123X), 然後放心的將 .cpp 轉為 .h, 再將那個不會出現的字串轉回 .cpp。由於是字串代換, 代換的目標沒有出現, 也不會有不良影響。這裡的順序很重要, 替代字串的順序對的話, 就可應付各種情況。

所以要支援其它副檔名轉為 .h, 只要在後面直接多加 :s 即可。其它就只是細部小修改, 沒改也沒什麼影響。

2012年3月14日 星期三

執行檔和檔案路徑注意事項

daemon 執行的時候, 通常會在程式裡 chdir 到 / 再開始做事, 藉此避免之後 daemon 所在的目錄無法被砍掉 (即使從檔案系統上下指令砍掉該目錄, 實際上它仍存在, 要等 daemon 結束才會真的釋放)。

程式執行中若需要寫入一些暫存檔, 要考慮到權限問題, 可以寫到 /tmp 最省事, 若需要永久保留, 考慮寫到 $HOME/.PROG/ 下, 不要直接寫到執行檔目錄下。平時開發寫到執行檔目錄下沒有問題, 但當需要將程式打包成套件, 裝到系統目錄時 (如 /usr/bin、/usr/local/bin), 就會有權限問題而無法寫入。

C++ 能否用 memcpy 複製 class / struct 的資料?

答案是: POD (plain old data) type 可以。POD type 可和 C 互通, CPP Reference POD Type 的介紹: Specifies that the type is POD (Plain Old Data) type. Thi...