泛型編程是 C++ 庫開發的基石,STL 容器、迭代器、算法皆建於此。本文系統梳理模板函數、類模板、非類型參數、模板特化、編譯模型等核心知識點,並釐清常見誤區。
泛型編程旨在編寫與類型無關的代碼。以 swap 為例,無模板時需為 int、double、string 分別重寫;模板化後,一份代碼服務所有支持拷貝賦值的類型。
template <typename T>
int func(const T& a1, const T& a2) { ... }
template <class T>
inline int func(const T& a1, const T& a2) { ... }
template <typename T1, typename T2, typename T3>
T1 func(const T2& t2, const T3& t3) { ... }
// 顯式指定模板實參
func<long>(i, log);
template <typename T1, typename T2, typename T3>
T3 func(const T1& t1, T2& t2) { ... }
// 調用時必須按聲明順序提供所有前置參數
func<long, int, long>(12, 34);
template <class Type>
class Queue { ... };
Queue<int> qi; // 顯式提供模板實參
template <class T, size_t N>
void func(T (&parm)[N]) { // N 直接用值替換
// 可獲取數組長度 N
}
普通函數/類:聲明在頭文件,定義在源文件,鏈接時解析。
模板不同:編譯器實例化時必須看到定義。因此模板頭文件通常同時包含聲明與定義。
// header.h
#ifndef XX_H_
#define XX_H_
template <typename T>
int func(T& t1, T& t2);
#include "impl.cpp" // 直接包含定義文件
#endif
// impl.cpp
template <typename T>
int func(T& t1, T& t2) { ... }
template 開頭,接類模板形參表Queue<T>::member_functemplate <class T>
ret_type Queue<T>::member_func_name() { ... }
必須是編譯時常量表達式:
template <int H, int W>
class Screen { ... };
Screen<24, 80> hp2621; // 正確
// Screen<h, w> hp; // 錯誤:非常量
template <class Type>
class Bar {
friend class FooBar; // FooBar 所有成員可訪問 Bar 任意實例私有成員
};
template <class Type>
class Bar {
template <class T> friend class Fool;
template <class T> friend void templ_fcnt(const T&);
};
template <class T> class Foo2;
template <class T> void templ_fcnt(const T&);
template <class Type>
class Bar {
friend class Foo2<char*>;
friend void templ_fcnt<char*>(char* const&);
};
template <class T> class Foo2;
template <class T> void templ_fcnt(const T&);
template <class Type>
class Bar {
friend class Foo2<Type>;
friend void templ_fcnt<Type>(const Type&);
};
聲明依賴性:特定友元需前置聲明模板;一般友元編譯器視為聲明。
成員函數擁有自己的模板參數:
template <class Type>
class Queue {
public:
template <class It>
Queue(It begin, It end) : head(0), tail(0) { copy_elems(begin, end); }
template <class Iter>
void assign(Iter, Iter);
private:
template <class Iter>
void copy_elems(Iter, Iter);
};
類外定義需包含兩層模板參數:
template <class Type> // 類模板參數
template <class Iter> // 成員模板參數
void Queue<Type>::assign(Iter begin, Iter end) { ... }
template <class Type>
class Bar {
public:
static std::size_t count() { return ctr; }
private:
static std::size_t ctr;
};
// 相同類型實例共享一個 static 成員
Bar<int> bar1, bar2;
size_t ct = Bar<int>::count();
// 初始化
template <class Type>
std::size_t Bar<Type>::ctr = 0;
當通用模板對特定類型行為不符預期時(如 char* 比較地址而非內容),需特化。
template <>
int compare<const char*>(const char* t1, const char* t2) {
return strcmp(t1, t2);
}
template <>
class Queue<const char*> { ... };
// 類外定義成員不加 template<>
void Queue<const char*>::push(const char* val) { ... }
// 原始模板
template <class T1, class T2>
class Tem { ... };
// 偏特化:固定 T2 為 int,T1 可變
template <class T1>
class Tem<T1, int> { ... };
// 使用
Tem<int, string> foo; // 原始模板
Tem<string, int> bar; // 偏特化版本
template <typename T1, typename T2> class Data<T1*, T2*>template <typename T1, typename T2> class Data<T1&, T2&>
解決方案:
1. 頭文件顯式實例化template class SeqList<int>;(不推薦:老編譯器不支持、實例化依賴調用者)
2. 推薦:聲明與定義放入同一文件.hpp或.h中
| 優點 | 缺點 |
|---|---|
| 代碼復用、節省資源、加速迭代、催生 STL | 代碼複雜難維護、編譯時間長 |
| 增強靈活性、類型安全 | 錯誤信息冗長難定位 |