爲什麼應該使用模塊(Module)替代頭文件(Header)?
頭文件糟透了!
衆所周知,C程序在編譯時一般會預處理頭文件:
常規解決辦法如下:
- LLVM_WHY_PREFIX_UPPER_MACROS
- LLVM_CLANG_INCLUDE_GUARD_H
- template<class _Tp>
- const _Tp& min(const _Tp &__a,
- const _Tp &__b);
- #include <windows.h>
- #undef min // because #define NOMINMAX
- #undef max // doesn’t
但結果依然不夠理想,比較一下代碼與程序大小你會發現:
另外,頭文件形式的可擴展性天生不足。假設有n個源文件,每個源文件引用了m個頭文件,那麼構建過程的開銷會是m×n。這在C++中表現得尤爲糟糕。所以預說處理頭文件是一個非常糟糕的解決方案。
C家族的模塊系統
模塊是什麼?
- 庫的接口(API)
- 庫的實現
使用“import”導入已命名的模塊:
import會在源文件中忽略預處理狀態,並且選擇性導入,所以彈性(resilience)非常好。
使用“import”會導入什麼?
- 函數、變量、類型、模板、宏,等等;
- 公開API——其它的都隱藏;
- 沒有特別的命名空間機制。
C/C++引入模塊會怎麼樣?
引入模塊的目標在於:
- 在源文件中指定模塊名稱;
- API公開;
- 沒有頭文件!
要編寫一個模塊非常簡單,只需要使用export:
但是這麼做會遇到很多遺留問題:
- 需要遷移現在基於頭文件的類庫;
- 與不支持模塊的編譯器的互操作性;
- 工具需要理解模塊;
所以應該使用引入模塊的過渡方案——直接從頭文件中構建模塊。這麼做有以下好處:
- 頭文件有利於互操作;
- 程序員不需要完全改變自己習慣的開發模式;
模塊地圖(Module Map)
模塊地圖是模塊的關鍵,用來定位模塊相關(子)模塊,包含以下功能:
- 模塊定義命名(子)模塊
- 頭文件在(子)模塊中包含命名頭文件的內容
保護傘頭文件(Umbrella Header)
- 保護傘頭文件會在其目錄下包含所有頭文件信息
- 使用通配符submodules (module *) 可以爲每一個包含的頭文件創建一個子模塊:
- AST/Decl.h -> ClangAST.Decl
- AST/Expr.h -> ClangAST.Expr
模塊編譯過程:
- 找到命名模塊的module map;
- 產生一個獨立編譯器實例;
- 在module map中解析頭文件。
編輯模塊文件過程:
- 在“import”聲明處導入模塊文件;
- 把模塊文件保存在緩存中待重用。
從頭文件到模塊化
從頭文件編程轉換到使用模塊非常簡單:
庫方面:合併複合定義的結構、函數、宏,並且爲頭文件導入依賴,最後編寫好模塊地圖;
開發者方面只需要從“#include”過渡到“import”:
- 把“#inlude”都換成“import”;
- 使用module maps確定(子)模塊(類似頭文件裏的“#include”);
當然,你也可以使用工具來自動化重寫代碼,非常簡單。
工具
編輯性能
使用模塊能夠提升語法解析性能:
- 模塊化的頭文件只需要解析一次,之後放在緩存中,於是m×n --> m+n
- 所有基於源(source-based)工具都能帶來好處
- 自動鏈接大大簡化了庫的使用
- 自動導入可以阻止“#include”帶來的可怕的調試結果
調試流
通過DWARF的雙程調試有損耗:
- 只能獲得“用過”類型和函數
- 丟失了行內函數、模板
另外調試過程還會出現信息冗餘
那使用模塊的調試又會怎樣?
1.提高了構建性能
- 編譯器發出的DWARF更少
- 鏈接器清除重複的DWARF也更少
2.提高了調試體驗
- 調試器的ASF精度非常完美
- 調試器不需要尋找DWARF
總結
總而言之,C/C++使用模塊化非常有潛力:
- 編譯/構建時間的縮短
- 修復各種預處理問題
- 更好的工具體驗
- 通過設計,能夠平穩地過渡
- Clang實現已經在進行了
這個Slide在Hacker News上引起了激烈討論,大部分網友還是贊成模塊化的方式:
baberman:對我來說沒什麼不便,而且還給出了過渡方案,可能會很適合某些C/C++項目。我們應該對任何提升C/C++性能的想法持開放態度。
greggman:預處理有什麼不好嗎?如果我不想用預處理,我完全可以使用Objective-C等。現在的機器性能已經夠強大了,import在編譯上的性能優勢對我來說沒有任何吸引力,我更喜歡C/C++的傳統方式。
msbarnett反駁greggman:我認爲這正是這份提議的精髓所在,你既可以保留使用#include,也可以從現在開始就轉向import模塊的方式。
_djo_:這個想法會非常有前途!要說缺點的話就是來得太遲了!
nkurz:我不同意“m個頭文件+n個源文件 --> m×n倍編譯性能”。只要使用“#ifndef _HEADER_H”就不會出現這個問題,或者,爲什麼不使用#include_once <header.h>來解決呢?預處理很可怕嗎?預處理確實有一些問題,但是卻是可以克服的。
SeoxyS:我不關心性能,現在性能已經不是問題了,我更關心怎樣給開發者更少的負擔。
各位CSDN網友是怎樣看的呢?歡迎一起討論。