巨集常被視為「必要之惡」,但善用其**編譯期展開、Token Pasting、Stringizing、條件編譯**特性,能解決函式無法做到的編譯期資訊保留、零成本切換、重構輔助等問題。本文精選四大實戰模式:除錯追蹤切換、舊函式無縫重導向、除錯版掛鉤不改簽章、對話框資料雙向自動綁定。
#ifdef _DEBUG
#define TRACE_FILE_LINE() do { \
CString msg; \
msg.Format(_T("File=%s, Line=%u\r\n"), __FILE__, __LINE__); \
CFile f(_T("log.txt"), CFile::modeCreate|CFile::modeWrite|CFile::modeNoTruncate); \
f.SeekToEnd(); f.Write(msg, msg.GetLength()); \
} while(0)
#else
#define TRACE_FILE_LINE() ((void)0)
#endif
// 使用
TRACE_FILE_LINE(); // Release 版直接消失,零指令開銷
關鍵:__FILE__/__LINE__ 在**巨集展開點**解析,若改用函式則永遠指向函式定義處。
// 舊介面
void Lock();
void Unlock();
// 新介面(需檔案/行號追蹤死鎖)
void NewLock(LPCTSTR file, UINT line);
void NewUnlock(LPCTSTR file, UINT line);
// 標頭檔巨集重定義
#define Lock() NewLock(__FILE__, __LINE__)
#define Unlock() NewUnlock(__FILE__, __LINE__)
效果:全專案數千處 Lock() 呼叫自動帶入位置資訊,**零修改呼叫端代碼**,重構神器。
#define Lock() Lock(); TRACE(_T("Lock at %s:%u\n"), __FILE__, __LINE__)
#define Unlock() Unlock(); TRACE(_T("Unlock at %s:%u\n"), __FILE__, __LINE__)
原理:展開後第一個 Lock() 再次匹配巨集,但編譯器偵測到遞迴展開風險,**停止展開並改匹配同名函式**。Release 版 TRACE 為空,自動還原原始呼叫。
痛點:DoModal 前後各需幾十行對應賦值,變數名同/異、型別轉換(float ↔ CString)處理繁瑣。
#define BEGIN_EXCHANGE(doc, dlg, dir) \
{ CSmartPtr pDoc(doc); CSmartPtr pDlg(dlg); BOOL toDlg = (dir); \
#define EXCHANGE(var) \
if (toDlg) Assign(pDlg->var, pDoc->var); else Assign(pDoc->var, pDlg->var)
#define EXCHANGE2(docVar, dlgVar) \
if (toDlg) Assign(pDlg->dlgVar, pDoc->docVar); else Assign(pDoc->docVar, pDlg->dlgVar)
#define END_EXCHANGE() }
template class CSmartPtr {
T* p; public: CSmartPtr(T* p_):p(p_){} T* operator->() { return p; }
};
void Assign(CString& to, const CString& from) { to = from; }
void Assign(CString& to, float from) { to.Format(_T("%.2f"), from); }
void Assign(float& to, const CString& from) { to = _ttof(from); }
// ... 雙向覆蓋 int, double, short, long, CTime 等
void DataExchange(Doc* doc, Dlg* dlg, BOOL toDlg) {
BEGIN_EXCHANGE(doc, dlg, toDlg)
EXCHANGE(m_nValue1);
EXCHANGE2(m_fRate, m_strRate); // 名稱不同
EXCHANGE(m_dtTime);
END_EXCHANGE()
}
// 呼叫
DataExchange(pDoc, &dlg, FALSE); // Doc → Dlg (顯示前)
if (dlg.DoModal() == IDOK)
DataExchange(pDoc, &dlg, TRUE); // Dlg → Doc (確認後)
MYAPP_EXCHANGE)防衝突。do { ... } while(0) 包裹,確保分號語法正確。/E 或 /P 輸出預處理結果,驗證展開正確性。constexpr/inline/template 解決的,**優先不用巨集**。「巨集是編譯期的『元編程』:能存取__FILE__/__LINE__、能改寫語法樹、能零成本消失。駕馭它,而非被它駕馭。」