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

C++ 模板實戰:設計支援 ANSI/Unicode 雙版本的 Windows API 封裝庫

     編輯:關於C語言

前言:Windows API 雙字元集的痛點

Windows API 為相容歷史套件,大多提供 ANSI (A 尾碼) 與 Unicode (W 尾碼) 兩個版本,例如 CreateFileA/CreateFileW、GetFileAttributesA/GetFileAttributesW。傳統封裝面臨兩難:

  • 方案一:單版本實作 + 字串轉換 → 效能損耗
  • 方案二:雙版本平行實作 → 程式碼重複、維護惡夢

本文展示如何運用函數模板 + 特徵模板,實現「零重複、零轉換、編譯期分派」的優雅封裝。

核心基礎設施:select_trait 特徵模板庫

1. 布林特徵定義巨集

#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; };

2. 編譯期類型選擇:select_type

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; };

3. 編譯期函數/變數選擇:select_variable

關鍵技巧:利用函數模板參數推演避免顯式指定繁瑣的函數指標類型。

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; }

原理:flag=true 時 select_type 選 int,匹配第一個 select_variable_impl 多載;反之選 long 匹配第二個。編譯器自動推演 T1/T2 為函數指標類型。

4. 字元類型特徵:is_ansi_char

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, char const, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, char volatile, true)
TEMPLATE_BOOL_TRAIT_SPEC1(is_ansi_char, char const volatile, true)

三大應用場景實戰

場景一:參數類型化 — 路徑判斷目錄/檔案

template <typename charT>
inline int IsDirectoryOrFile(const charT* path) {
    DWORD dwFlag = select_variable<is_ansi_char<charT>::value>(
        GetFileAttributesA, GetFileAttributesW)(path);
    
    if (INVALID_FILE_ATTRIBUTES == dwFlag) return 0;
    return (dwFlag & FILE_ATTRIBUTE_DIRECTORY) ? 1 : 2;
}

呼叫端可直接傳入 char* 或 wchar_t*,編譯期自動選擇正確 API。

場景二:返回值類型化 — 取得執行檔路徑

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;
}

呼叫 GetExePath<char>() 得到 std::string,GetExePath<wchar_t>() 得到 std::wstring。

場景三:內部實現類型化 — 遞迴計算目錄大小

template <typename charT>
inline ULONGLONG GetDirSize(const charT* path) {
    int ret = IsDirectoryOrFile(path);
    if (0 == ret) return 0;

    std::basic_string<charT> strPath = path;
    if (1 == ret) {
        if (strPath.length() - 1 != strPath.rfind((charT)'\\'))
            strPath += (charT)'\\';
        strPath += select_variable<is_ansi_char<charT>::value>("*.*", L"*.*");
    }

    ULONGLONG ullSumSize = 0;
    // 關鍵:結構體類型也需根據字元型別選擇
    typename select_type<is_ansi_char<charT>::value, 
        WIN32_FIND_DATAA, WIN32_FIND_DATAW>::type findData;

    HANDLE hFindFile = select_variable<is_ansi_char<charT>::value>(
        FindFirstFileA, FindFirstFileW)(strPath.c_str(), &findData);

    for (BOOL bResult = (hFindFile != INVALID_HANDLE_VALUE);
         bResult;
         bResult = select_variable<is_ansi_char<charT>::value>(
             FindNextFileA, FindNextFileW)(hFindFile, &findData)) {
        if (findData.cFileName[0] == (charT)'.') continue;
        
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            strPath = strPath.substr(0, strPath.rfind((charT)'\\') + 1) 
                    + findData.cFileName;
            ullSumSize += GetDirSize(strPath.c_str());
        } else {
            ullSumSize += (((ULONGLONG)findData.nFileSizeHigh) << 32) 
                        + findData.nFileSizeLow;
        }
    }
    FindClose(hFindFile);
    return ullSumSize;
}

設計精華總結

維度傳統巨集做法模板特徵做法
程式碼重複高 (A/W 兩套)零 (單一模板)
執行期開銷字串轉換成本零 (編譯期分派)
類型安全弱 (巨集無類型檢查)強 (模板參數推演)
擴充性難 (新增 API 需改巨集)易 (只需新增特化)

現代 C++ 演進建議

  • C++17 起可用 if constexpr 簡化 select_variable 邏輯
  • C++20 Concepts 可替代特徵模板,提供更清晰的介面約束
  • 實務專案建議參考 ATL::CString、WTL 或 Windows Runtime Library (WRL) 成熟封裝

結語

透過特徵模板將「字元類型」與「API 版本選擇」綁定,透過函數模板參數推演消除函數指標類型的冗長宣告,這正是 C++ 模板元編程「零抽象成本」哲學的經典實踐。掌握此模式,不僅能優雅解決 Windows API 雙版本難題,更能舉一反三應用於任何「編譯期根據類型分派實作」的場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved