C/C++ 的可變參數函數(如 printf 格式字串)與自訂例外類別是進階程式設計常用技巧。本文透過一個 CComboProp 類別實例,示範如何使用 va_list 處理不定數量參數,並定義繼承自 CException 的自訂例外 CSelItemException 進行錯誤傳遞。
va_start(va_list, last_fixed_param):取得第一個可變參數位址。va_arg(va_list, type):依指定型別取得下一個參數並移動指標。va_end(va_list):清理堆疊狀態。約定:最後一個參數必須為 NULL 或 '\0' 作為結束標記。
BOOL CComboProp::AddOption(int nSeledIndex, LPCTSTR ItemString1, ...)
{
va_list argList;
va_start(argList, nSeledIndex);
_variant_t vtDefault;
int nCount = 0;
LPCTSTR strArg;
for (strArg = va_arg(argList, LPCTSTR); strArg != NULL; strArg = va_arg(argList, LPCTSTR)) {
CBCGPProp::AddOption(strArg, FALSE);
if (nSeledIndex == nCount++) vtDefault.SetString(strArg);
}
va_end(argList);
if (nSeledIndex >= 0) {
if (nSeledIndex < nCount) this->SetValue(vtDefault);
else throw CSelItemException(nSeledIndex, nCount); // 索引超出範圍拋出例外
}
return TRUE;
}
CComboBox* CComboProp::CreateCombo(CWnd* pWndParent, CRect rect,
int SeledIndex, LPCTSTR ItemString1, ...)
{
CComboBox* pCombo = CBCGPProp::CreateCombo(pWndParent, rect);
va_list argList;
va_start(argList, SeledIndex);
LPCTSTR strArg;
for (strArg = va_arg(argList, LPCTSTR); strArg != NULL; strArg = va_arg(argList, LPCTSTR)) {
pCombo->AddString(strArg);
}
va_end(argList);
if (SeledIndex >= 0) {
if (pCombo->GetCount() > SeledIndex) {
if (pCombo->SetCurSel(SeledIndex) == CB_ERR)
throw CSelItemException(SeledIndex, pCombo->GetCount());
} else {
throw &CSelItemException(SeledIndex, pCombo->GetCount()); // 指標拋出需拷貝建構子
}
}
return pCombo;
}
class CSelItemException : public CException {
public:
CSelItemException(const int& SelIndex, const int& ItemCount) {
m_IndextoSeled = SelIndex; m_ItemCount = ItemCount;
}
// 必須提供拷貝建構子,否則 throw 物件會編譯錯誤
CSelItemException(CSelItemException& m) {
m_IndextoSeled = m.m_IndextoSeled; m_ItemCount = m.m_ItemCount;
}
virtual ~CSelItemException() {}
int m_IndextoSeled; // 嘗試選取的索引
int m_ItemCount; // 實際項目總數
};
關鍵點:
CException 才能搭配 MFC TRY/CATCH 巨集。throw CSelItemException(...) 會因無法複製物件而編譯失敗(錯誤 C2153)。throw CSelItemException(...)(值傳遞)或 throw &CSelItemException(...)(指標傳遞),後者需確保物件生命週期。
TRY {
pProp->AddOption(5, _T("A"), _T("B"), NULL); // 只有 2 項,索引 5 會失敗
}
CATCH(CSelItemException, e) {
CString msg; msg.Format(_T("索引 %d 超出範圍 (總項目 %d)"), e->m_IndextoSeled, e->m_ItemCount);
AfxMessageBox(msg);
}
END_CATCH
掌握 va_list 三巨集與自訂例外類別撰寫規範,能讓函式介面更彈性、錯誤處理更物件化、更符合 MFC 架構風格。