所謂頭文件發布,就是在build某個工程的build過程中,把頭文件發布到特定的輸出目錄,而依賴於此工程的代碼,則需要從此特定的輸出目錄來include頭文件。換句話說,在這種做法下,頭文件與最終產生的library/binary具有同等地位,它也是build過程的一個產出。
我們寫C++代碼,一般都是直接從source目錄包含所需要的頭文件的,那麼為什麼要使用這種頭文件發布的方式呢? 我們可以先分析一下不發布頭文件可能帶來的問題:
因為直接從source目錄包含頭文件,我們無法控制哪些頭文件可以include,而哪些不可以。因為很多情況下,我們很可能只想對用戶暴露某個層次的api,但對於用戶來說,因為他們在同一目錄下,包含任何頭文件都是同等方便的。這明顯提高了犯錯的可能性。
如果build不完整,有些問題要到鏈接時才能發現。
舉個例子,如果A依賴於B,你在B中新增加了個函數,在A中使用。然後你在沒有build B的情況下直接build了A,這個錯誤無法在編譯期檢測出來,而只有把所有源文件編譯完了鏈接的時候你才被通知有這麼個錯誤,對於大工程,這會是個問題。
如果build不完整,有些問題在運行時才能發現- 這就是bug了
舉個例子,還是A依賴於B,你在B中修改了一個宏定義或者常量定義,如把#define PI 3.14改成了#define PI 3.1415926,然後你又忘了build B而直接去build A了,此時編譯沒有問題,但是此時模塊B與模塊A中對PI的定義就不一致了,必然會造成運行時問題,這種問題要更難發現。
所以,為了保證C++代碼中接口與binary的完全統一,包括可見性與行為上的統一,使用頭文件發布是非常有效的一個方法,對於上述問題:
通過只把需要暴露的頭文件發布到特定目錄,有效的杜絕了用戶”包含不該包含“的頭文件的問題
因為B中被更新的頭文件未被發布,該問題被提前到編譯期被發現
因為B中被更新的頭文件未被發布,此時A與B的binary中使用的PI,都是未更新的3.14,從而保證了一致性。
要實現頭文件發布,其實也蠻簡單,主要是編譯設置上的事,對源代碼並沒有影響:
為方便說明,還是用A依賴於B為例:
在build B時,把B的頭文件拷貝到與輸出目錄bin同級的目錄,比如include目錄
在build A時,把上面提到的include目錄作為包含目錄
如果你用Visual Studio的話,可以用post build event;用gmake的話,可以新加一個publish header的rule。
當然,如果你有一個高度智能的build system,這個過程可以完全自動化,比如我們team現在實現的一個,只需指明dependency關系,發布頭文件,設置包含目錄都自動完成,大大簡化了build的維護
作者 lzprgmr