利用「宏定義 → 包含頭文件 → 取消宏」的循環,可讓同一個頭文件在不同編譯上下文中展開為完全不同的代碼。這是 X-Macro 模式的核心應用,廣泛用於枚舉/字串表/序列化/命令表自動同步。
// 1. 定義「資料表」頭文件(無護衛宏!)
// funcs.def
FUNC_1(ID_FUN1_001)
FUNC_1(ID_FUN1_002)
FUNC_2(ID_FUN2_001)
FUNC_2(ID_FUN2_002)
// 2. 第一次包含:生成 enum aaa(僅保留 FUNC_1 項)
#define FUNC_1(opt) opt,
#define FUNC_2(opt) /* 空 */
enum aaa {
#include "funcs.def"
ID_FUN1_END
};
#undef FUNC_1
#undef FUNC_2
// 3. 第二次包含:生成 enum bbb(僅保留 FUNC_2 項)
#define FUNC_1(opt) /* 空 */
#define FUNC_2(opt) opt,
enum bbb {
#include "funcs.def"
ID_FUN2_END
};
#undef FUNC_1
#undef FUNC_2
enum aaa {
ID_FUN1_001,
ID_FUN1_002,
ID_FUN1_END
};
enum bbb {
ID_FUN2_001,
ID_FUN2_002,
ID_FUN2_END
};
| 傳統做法 | X-Macro 做法 |
|---|---|
| 手寫兩份 enum,新增項需同步修改兩處 | 只維護 funcs.def 一份資料源 |
| 易漏改、錯改,導致枚舉值與字串表不一致 | 編譯期保證同步,零運行開銷 |
| 擴展新屬性(如描述、權限)需改動所有使用點 | 只需在 .def 增加欄位,宏端按需取用 |
// cmds.def
CMD(LOGIN, 1001, "用戶登入", true)
CMD(LOGOUT, 1002, "用戶登出", true)
CMD(CHAT, 2001, "聊天消息", false)
// 生成命令碼 enum
#define CMD(name, id, desc, auth) name = id,
enum CmdId { #include "cmds.def" };
#undef CMD
// 生成描述字串表
#define CMD(name, id, desc, auth) [id] = desc,
const char* cmd_desc[] = { #include "cmds.def" };
#undef CMD
// 生成權限檢查 switch
#define CMD(name, id, desc, auth) case id: return auth;
bool cmd_need_auth(int id) {
switch (id) {
#include "cmds.def"
}
return false;
}
#undef CMD
.def 或 .inc 後綴,區別於普通頭文件。.def 文件不能有 #ifndef,否則二次包含失效。#include 前後緊跟 #define/#undef,避免汙染全局命名空間。gcc -E 檢查展開結果,納入 CI 流程。「多次包含同一頭文件,每次宏定義不同」是 C 預處理器圖靈完備性的經典展現。掌握 X-Macro,能將資料與代碼分離,實現單一資料源、多重視圖,大幅降低維護成本與不一致風險。這不是花招,而是系統級 C 開發的標準工程實踐。