首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C/C++ 巨集進階妙用:條件編譯、函式攔截、自動化資料交換與除錯追蹤

     編輯:關於C++

巨集常被視為「必要之惡」,但善用其**編譯期展開、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 為空,自動還原原始呼叫。

模式四:對話框資料雙向自動交換(DataExchange 模式)

痛點: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; }
};

Assign 多載處理型別轉換

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) 包裹,確保分號語法正確。
  • 偵錯巨集展開:MSVC /E 或 /P 輸出預處理結果,驗證展開正確性。
  • 現代 C++ 能用 constexpr/inline/template 解決的,**優先不用巨集**。
「巨集是編譯期的『元編程』:能存取 __FILE__/__LINE__、能改寫語法樹、能零成本消失。駕馭它,而非被它駕馭。」
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved