C++ 泛型程式設計中,Traits(特徵)與 Policy(策略) 是解決「類型相關行為」與「可配置行為」的兩大核心手法。本文以一個看似簡單的「陣列求和」函數為切入點,層層剖析返回型別推導與初始值語意的陷阱,展示 Traits 技術如何優雅化解這些問題,並引入 Policy 概念實現使用者自訂行為。
template <typename T>
T Total(const T* beg, const T* end) {
T total = T(); // 預設建構
while (beg != end) total += *beg++;
return total;
}
測試 int a[5]={0,1,2,3,4} 得 10,正確;但 char b[5]={'a','b','c','d','e'} 卻得到 -17。原因在於 char 範圍過小,累加溢位。同理,int 陣列若總和超過 INT_MAX 也會溢位。這揭示了兩個核心問題:
char 應回傳 int;輸入 int 應回傳 long;輸入 float 應回傳 double。template <typename R, typename T>
R Total(const T* beg, const T* end) {
R total = R();
while (beg != end) total += *beg++;
return total;
}
// 呼叫:Total<int>(b, b+5) // 必須顯式指定 R
缺點:呼叫端必須顯式傳遞返回型別,違背「預設正確、可選覆寫」的直覺介面。
定義 TotalTrait<T>,以 typedef R 表達「輸入 T 對應的累加型別 R」,並以 static R Zero() 提供正確初始值:
template <typename T> struct TotalTrait; // 僅宣告,不定義
template <> struct TotalTrait<char> { typedef int R; static R Zero() { return 0; } };
template <> struct TotalTrait<int> { typedef long R; static R Zero() { return 0; } };
template <> struct TotalTrait<float> { typedef double R; static R Zero() { return 0.0; } };
// 使用者自訂型別同理特化
改寫演算法:
template <typename T>
typename TotalTrait<T>::R Total(const T* beg, const T* end) {
typedef typename TotalTrait<T>::R R;
R total = TotalTrait<T>::Zero();
while (beg != end) total += *beg++;
return total;
}
// 呼叫:Total(b, b+5) // 自動推導,零負擔
關鍵細節:typename TotalTrait<T>::R 中的 typename 告訴編譯器「這是一個型別」,否則會被誤判為靜態成員變數導致語法錯誤。
若需支援「累積」,只需在 Traits 增加 static R Init(const R& base) 或直接讓 Zero() 可被覆寫,演算法改為 R total = TotalTrait<T>::Init(user_base),即可無縫切換「求和」與「累積」語意。
有時使用者確知 int 陣列總和不會溢位,希望保持 int 返回以節省空間或符合介面。引入 Policy 類別 作為第二模板參數並提供預設值:
template <typename T, typename Policy = TotalTrait<T> >
class CTotal {
public:
static typename Policy::R Total(const T* beg, const T* end) {
typedef typename Policy::R R;
R total = Policy::Zero();
while (beg != end) total += *beg++;
return total;
}
};
// 便利函數保持原呼叫風格
template <typename T>
typename TotalTrait<T>::R Total(const T* beg, const T* end) {
return CTotal<T>::Total(beg, end);
}
template <typename T, typename Policy>
typename TotalTrait<T>::R Total(const T* beg, const T* end) {
return CTotal<T, Policy>::Total(beg, end);
}
現在使用者可:
Total(b, b+5) —— 自動用 TotalTrait<char> 回傳 intTotal<char, MyPolicy>(b, b+5) —— 自訂策略回傳 char 或其他型別「Traits 提供了一種配置具體元素(通常是型別)的途徑,而該途徑主要是用於泛型計算。」
本文展示的演進路徑——問題發現 → 型別分離 → Traits 封裝對應關係 → 初始值語意泛化 → Policy 參數化策略——正是現代 C++ 庫設計(如 STL 配置器、std::char_traits、Boost 型別特徵)的縮影。下一篇將深入探討 Policy 類別的組合與更複雜的泛型演算法框架設計。