Windows API 多數提供 ANSI (xxxA) 與 Unicode (xxxW) 兩種版本。封裝自有 API 時,傳統做法有二:
本文展示如何利用 **函數模板 + 特徵萃取** 同時達成 **零轉換效率** 與 **零重複程式碼**。
// 布林特徵基礎巨集
#define TEMPLATE_BOOL_TRAIT_DEF1(trait, T, c) \
template<typename T> struct trait { static const bool value = c; };
#define TEMPLATE_BOOL_TRAIT_SPEC1(trait, sp, c) \
template<> struct trait<sp> { static const bool value = c; };
// 類型選擇
template<bool flag, typename T1, typename T2> struct select_type;
template<typename T1, typename T2> struct select_type<true, T1, T2> { typedef T1 type; };
template<typename T1, typename T2> struct select_type<false, T1, T2> { typedef T2 type; };
// 變數/函數指標選擇 (關鍵:利用多載解析)
template<bool flag, typename T1, typename T2>
inline typename select_type<flag, T1, T2>::type select_variable(T1 t1, T2 t2) {
return select_variable_impl(typename select_type<flag, int, long>::type(), t1, t2);
}
template<typename T1, typename T2> inline T1 select_variable_impl(int, T1 t1, T2 t2) { return t1; }
template<typename T1, typename T2> inline T2 select_variable_impl(long, T1 t1, T2 t2) { return t2; }
// 字元類型特徵:char 為 ANSI,wchar_t 為 Unicode
TEMPLATE_BOOL_TRAIT_DEF1(is_ansi_char, T, false)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, const char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, volatile char, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, const volatile char, true)
#undef TEMPLATE_BOOL_TRAIT_DEF1
#undef TEMPLATE_BOOL_TRAIT_SPEC1
template<typename CharT>
inline int IsDirectoryOrFile(const CharT* path) {
DWORD dwFlag = select_variable<is_ansi_char<CharT>::value>(GetFileAttributesA, GetFileAttributesW)(path);
if (dwFlag == INVALID_FILE_ATTRIBUTES) return 0;
return (dwFlag & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 2;
}
template<typename T>
inline std::basic_string<T> GetExePath() {
T szExePath[MAX_PATH];
select_variable<is_ansi_char<T>::value>(GetModuleFileNameA, GetModuleFileNameW)(NULL, szExePath);
return szExePath;
}
template<typename CharT>
inline ULONGLONG GetDirSize(const CharT* path) {
int ret = IsDirectoryOrFile(path);
if (ret == 0) return 0;
std::basic_string<CharT> strPath = path;
if (ret == 1) { // 目錄
if (strPath.back() != '\\') strPath += '\\';
strPath += select_variable<is_ansi_char<CharT>::value>("*.*", L"*.*");
}
ULONGLONG sum = 0;
// 選擇正確的 WIN32_FIND_DATA 結構
typename select_type<is_ansi_char<CharT>::value, WIN32_FIND_DATAA, WIN32_FIND_DATAW>::type findData;
HANDLE hFind = select_variable<is_ansi_char<CharT>::value>(FindFirstFileA, FindFirstFileW)(strPath.c_str(), &findData);
for (BOOL bRes = (hFind != INVALID_HANDLE_VALUE); bRes;
bRes = select_variable<is_ansi_char<CharT>::value>(FindNextFileA, FindNextFileW)(hFind, &findData)) {
if (findData.cFileName[0] == '.') continue;
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
strPath = strPath.substr(0, strPath.rfind('\\') + 1) + findData.cFileName;
sum += GetDirSize(strPath.c_str());
} else {
sum += (((ULONGLONG)findData.nFileSizeHigh) << 32) + findData.nFileSizeLow;
}
}
FindClose(hFind);
return sum;
}
| 維度 | 傳統雙版本 | 模板特徵法 |
|---|---|---|
| 程式碼重複 | 高 (兩份完整函數) | **零** (單一模板) |
| 執行效率 | 高 (無轉換) | **高** (編譯期解析,零轉換) |
| 型別安全 | 易出錯 (巨集切換) | **強** (模板參數推導) |
| 擴充性 | 新增 API 需改兩處 | **單點維護** |
is_ansi_char 等特徵為 **單一真實來源**,避免巨集汙染全域命名空間。