原文鏈接http://blog.csdn.net/breakerzy/article/details/7426458
關於 C++ 模板編程的慣用法,note-to-self + keynote + idiom case + cross-reference 式筆記
模板語法^
稱謂:函數模板 (function template) vs. 模板函數 (template function),或 類模板 (class template) vs. 模板類 (template class),見 [CPP TEMP] 7.1
兩階段編譯 (two-phase lookup)、第一實例化點 (first point of instantiation),見 [CPP LANG] 13.2.5 [CPP TEMP] 2.1.2
實例化 (instantiation) 和特化(又譯專門化)(specialization)、生成的特化 (generated specialization) vs. 顯式的特化 (explicit specialization),見 [CPP LANG] 13.2.2, C.13.7 [CPP TEMP] 7.2
不完全實例化 (incomplete instantiation),見 [CPP LANG] 13.2.2 [MODERN CPP] 1.8
完全特化 (complete specialization)、部分特化(又譯偏特化)(partial specialization),見 [CPP LANG] 13.5 [CPP TEMP] 3.3, 3.4
特化順序:更特化 (more specialized)、更泛化 (more general)、原始模板 (primary template),見 [CPP LANG] 13.5.1 [CPP TEMP] 7.2
非類型模板參數,見 [CPP LANG] 13.2.3 [CPP TEMP] 4
函數模板和普通函數間的重載,見 [CPP LANG] 13.3.2 [CPP TEMP] 2.4
函數模板的參數推導 (argument deduction),見 [CPP LANG] 13.3.1, C.13.4 [CPP TEMP] 2.2, 11
默認模板參數,見 [CPP LANG] 13.4.1 [CPP TEMP] 3.5
成員模板 (member template),見 [CPP LANG] 13.6.2 [CPP TEMP] 5.3
模板作為模板參數 (template template parameter),見 [CPP LANG] C.13.3 [CPP TEMP] 5.4
typename 限定詞 (typename qualifier),見 [CPP LANG] C.13.5 [CPP TEMP] 5.1 [EFFECT CPP] Item 42
template 限定詞 (template qualifier),見 [CPP LANG] C.13.6 [CPP TEMP] 5.1
模板代碼組織:包含模型 (inclusion model) vs. 分離模型 (separation model) 又稱分別編譯 (separate compile),見 [CPP LANG] 13.7 [CPP TEMP] 6.1, 6.3
顯式實例化 (explicit instantiation),見 [CPP LANG] C.13.10 [CPP TEMP] 6.2
設計思維:運行時多態 (run-time polymorphism) vs. 編譯時多態 (compile-time polymorphism) 又稱參數化多態 (parametric polymorphism),見 [CPP LANG] 13.6.1 [EFFECT CPP] Item 41 [CPP TEMP] 14
模板慣用法示例^
堆棧上分配^
on-stack allocation 的 std 案例:tr1::array 模板
使用模板方法的不足之處是使用編譯時確定的 buffer size,為了能在運行時動態調整 stack 內存分配數量,可借助 VC CRT 的 _alloca, _malloca 函數
示例:一個 printf 式的生成 std::string 的函數
- template <size_t BufSize, class CharT>
- inline
- std::basic_string<CharT> make_string(const CharT* format, ...)
- {
- CharT buf[BufSize];
- va_list args;
- va_start(args, format);
- // vsprintf 是函數模板, 其針對 char 特化調用 vsprintf_s, 針對 wchar_t 特化調用 vswprintf_s
- vsprintf(buf, BufSize, format, args);
- va_end(args);
- // 注意: 返回時構造可讓 VC 編譯優化為只有一次 string ctor 調用, 沒有額外 copy
- return std::basic_string<CharT>(buf);
- }
編譯優化的開關^
bool 模板參數,或整數模板參數 + 閾值,避免重復代碼時借助編譯優化
示例:一個支持透明色的 32bit blit 函數
- template <bool UseMask>
- void blit(int* dst, const int* src, int mask, size_t size)
- {
- for (size_t i = 0; i < size; i++, dst++, src++) {
- if (!UseMask || *src != mask)
- *dst = *src;
- }
- }
推導數組元素個數^
可由參數推導求出數組的元素個數,要求必須是數組名,而非指向數組的指針或 new[] 數組
示例:VC CRT 的 _countof 計算數組的元素個數
- // 以 C++ 方式編譯時, _countof 的定義如下
- template <typename _CountofType, size_t _SizeOfArray>
- char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
- #define _countof(_Array) (sizeof(*__countof_helper(_Array)) + 0)
示例:多數 VC CRT buffer 操作函數都有 Secure Template Overloads 版本
- template <size_t size>
- errno_t strcpy_s(char (&strDestination)[size], const char *strSource);
推導常數^
示例:掩碼常數 Mask<N>::Value
- #define _MASK_VAL(x) (1 << x)
- // 用於代替上面的宏
- // 實例化超出 [0:31] 范圍的 Mask 時, 產生編譯警告 warning C4293
- template <unsigned int N>
- struct Mask {
- enum { Value = (1 << N) };
- };
示例:GCD<N, M>::Value 求解最大公約數
- // GCD<N, M> 原始模板
- template <unsigned int N, unsigned int M>
- struct GCD {
- static const unsigned int Value = GCD<M, N % M>::Value;
- };
- // GCD<N, 0> 特化, 用以終止遞歸條件
- template <unsigned int N>
- struct GCD<N, 0> {
- static const unsigned int Value = N;
- };
隱式轉換的顯式函數 implicit_cast^
見 [CPP LANG] 13.3.1 模板函數參數推導
- 因為是 return u,而不是 return (T) u,所以是隱式轉換
- 可推導的參數放到 template 參數列表的最后
- 效率:有兩次拷貝(參數、返回值),但通常編譯優化可將其減小到一次拷貝
- template <class T, class U> T implicit_cast(U u) { return u; }
- void func(int i)
- {
- implicit_cast<double>(i); // T 顯式指定為 double, U 由參數推導得出 int
- implicit_cast<char, double>(i); // i 先轉換為 double, 再隱式轉換為 char
- implicit_cast<char*>(i); // 錯誤: int 不能隱式轉換為 char*
- }
推導 callable 可調用物^
-
基於函數指針類型
- 可提取 callable 的參數和返回值類型
- callable 只接受函數指針
- template <class RetT, class ArgT>
- bool calc_and_check_1(RetT (*calc)(ArgT), ArgT arg, bool (*check)(RetT))
- {
- const RetT& ret = calc(arg);
- return check(ret);
- }
-
基於直接的類型
- callable 接受函數指針、函數對象、成員函數
- std 算法使用這種方法
提取 callable 的參數和返回值類型時,callable 只能是函數對象:
- 函數對象類:含 typedef 指明參數和返回值類型,std 約定命名為 [first_|second_]argument_type, result_type,可從 binary_function, unary_function 繼承
- 函數指針:用 std::ptr_fun 轉換為函數對象
- 成員函數:用 std::mem_fun, tr1::bind 轉換為函數對象
- template <class Calc, class ArgT, class Check>
- bool calc_and_check_2(Calc calc, ArgT arg, Check check)
- {
- const Calc::result_type& ret = calc(arg);
- return check(ret);
- }
-
使用 tr1::function 或 boost::function
- 和第 2 種方法類似,但是 callable 的參數和返回值類型是固定的
- tr1::function 已放入 std 名字空間
- bool calc_and_check_3(std::function<int (double)> calc, double arg, std::function<bool (int)> check)
- {
- const std::function<int (double)>::result_type& ret = calc(arg);
- return check(ret);
- }
用成員模板實現繼承隱喻^
見 [CPP LANG] 13.6.3.1
對於 Derived 和 Base class,並不隱喻 T<Derived> 和 T<Base> 之間存在內建的繼承關系。更一般的:設 TypeA 到 TypeB 有隱式轉換,並不隱喻 T<TypeA> 到 T<TypeB> 存在內建的隱式轉換
可用成員模板實現 subclass 轉換隱喻。通常隱式轉換以這些成員函數表現:copy ctor, assign, operator Type,即實現這些成員函數的模板版本
示例:典型的 std::auto_ptr 實現,auto_ptr<Derived> 可拷貝或賦值給 auto_ptr<Base>
- template <class Type>
- class auto_ptr {
- // 非模板的 copy ctor, assign 無法從下面的模板版生成, 需要顯式寫出, 否則將使用編譯器生成的
- auto_ptr(auto_ptr& right) throw() : ptr_(right.release()) {}
- // 模板的 copy ctor: 當 T2* => Type* 時, 隱喻 auto_ptr<T2> => auto_ptr<Type>
- // assign 和 operator auto_ptr<T2> 的模板版相似從略
- template <class T2>
- auto_ptr(auto_ptr<T2>& right) throw() : ptr_(right.release()) {}
- Type* release() throw() {
- Type* temp = ptr_;
- ptr_ = 0;
- return temp;
- }
- private:
- Type* ptr_;
- };
假設模板基類中的成員^
模板會小小的違背繼承規則:Derived<T> 不能訪問 Base<T> 的成員,除非告訴編譯器假設它存在,即推遲檢查到第二階段編譯,見 [EFFECT CPP] Item 43 [CPP TEMP] 9.4.2
假設 Base<T> 的成員 member 存在的方法有:
- 用 this->member
- 用 using Base<T>::member 導入名字
- 用 Base<T>::member,副作用是關閉 virtual member 的動態綁定
CRTP 循環模板模式^
見 [CPP TEMP] 16.3 CRTP: Curiously Recurring Template Pattern
示例:使用類專屬的 set_new_handler 和 operator new,見 [EFFECT CPP] Item 49
- template <class T>
- class NewHandlerSupport; // 含針對類型 T 的 set_new_handler 和 operator new
- class Widget : public NewHandlerSupport<Widget> {};
結合使用函數模板和類模板^
- 類模板能以函數對象類的形式保存調用環境,並有模板默認參數,但不能推導參數
- 函數模板能推導參數,但不能保存環境和有模板默認參數
兩者結合后,用來寫生成函數對象的工廠函數
std 案例:用於構造謂詞的 binder, adapter 和 negater,見 [CPP LANG] 18.4.4
特化的基本目的^
-
解決對於特定類型實例化時的語義錯誤
示例:數組字符串的比較
- // 原始模板使用值比較
- template <class T>
- bool less(T l, T r)
- {
- return l < r;
- }
- // const char* 應該用 strcmp 比較
- template <>
- bool less(const char* l, const char* r)
- {
- return strcmp(l, r) < 0;
- }
-
為特定類型實例化提供更高效的實現
示例:交換 STL 容器 string, vector 等
- // 原始模板使用值交換
- template <class T>
- void swap(T& l, T& r)
- {
- T t(l);
- l = r;
- r = t;
- }
- // std::string 最好用 swap 成員函數, 以發揮 pimpl 方式實現(假設)的效能
- template <class CharT>
- void swap(std::basic_string<CharT>& l, std::basic_string<CharT>& r)
- {
- l.swap(r);
- }
-
主要依靠特化工作,反而原始模板的意義為次,traits 通常使用這種方法
示例:編譯時 assert 斷言
- // 靜態/編譯時 assert 斷言, 要求 expr 能在編譯時求值
- // 如果 expr = false, 產生編譯錯誤 error C2027
- template <bool expr> struct StaticAssert;
- template <> struct StaticAssert<true> {};
- template <size_t size> struct StaticAssertTest {};
- #define STATIC_ASSERT(x) \
- typedef StaticAssertTest<sizeof(StaticAssert<bool(x)>)> StaticAssertType##__LINE__
解決實例化的代碼膨脹^
用提取共性的方法解決實例化產生的目標代碼膨脹 (code bloat),見 [EFFECT CPP] Item 44
示例:部分特化轉接調用完全特化解決代碼膨脹,見 [CPP LANG] 13.5
- // 原始模板
- template <class T>
- class Vector; // 含最一般的 [] 操作
- // Vector<void*> 是針對 void* 的完全特化
- template <>
- class Vector<void*>; // 含針對 void* 的 [] 操作
- // Vector<T*> 是針對任意類型 T 指針的部分特化
- template <class T>
- class Vector<T*> : private Vector<void*> {
- typedef Vector<void*> Base;
- // 轉接調用完全特化 Vector<void*> 的 [] 操作
- T*& operator[](int i) {
- return reinterpret_cast<T*&>(Base::operator[](i));
- }
- };
- // 部分特化 Vector<T*> 的實例化
- // 代碼空間開銷: 所有的 Vector<T*> 實際共享一個 Vector<void*> 實現
- Vector<Shape*> vps; // <T*> 是 <Shape*>, T 是 Shape
- Vector<int**> vppi; // <T*> 是 <int**>, T 是 int*
traits 特征和 policy 策略^
見 [CPP TEMP] 15 [CPP LANG] 13.4 [MODERN CPP] almost all book
traits 和 policy 使用相同的下層技術,只是在設計目的和作用上不同
-
traits 的作用傾向於:特征提取,特征主要指類型和標識,以含 typedef, enum, static 著稱,經常是空類
traits 的 std 案例:std::iterator_traits,用法舉例:根據 iterator 特征選擇算法,如只對 random_access_iterator_tag 使用快速排序
-
policy 的作用傾向於:行為組合,是編譯時 Strategy 模式,以含行為正交的 member function 著稱,經常多個 policy 組合而用
policy 的 std 案例:std::allocator,用法舉例:實現自己的 small-block allocator 讓 STL 容器針對小塊內存分配優化
示例:traits 類型標識
Q: 為什么不用 C++ 內建的 RTTI: typeid()
A: 即便 MyTraits<Type> 的 Type 不是多態類型,typeid() 可在編譯時求值,但 type_info::operator== 或 type_info::name() + strcmp() 卻很難在編譯時求值,導致條件分支沒法優化掉,見 [EFFECT CPP] Item 47, 48
- struct MyTraitsBase {
- enum {
- TYPE_UNKNOWN,
- TYPE_A,
- TYPE_B
- };
- };
- template <class Type>
- struct MyTraits : public MyTraitsBase {
- static const int TYPE_ID = TYPE_UNKNOWN;
- };
- template <>
- struct MyTraits<TypeA> : public MyTraitsBase {
- static const int TYPE_ID = TYPE_A;
- };
- template <>
- struct MyTraits<TypeB> : public MyTraitsBase {
- static const int TYPE_ID = TYPE_B;
- };
- template <class Type>
- void user(const Type& obj)
- {
- // 實際中可用表驅動法替代判斷語句
- // 開啟編譯優化后, 這里的條件分支被優化掉
- if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_A)
- // 針對 TypeA 類型的處理
- else if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_B)
- // 針對 TypeB 類型的處理
- else if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_UNKNOWN)
- // 針對一般類型的處理
- }
參考書籍^
- [CPP LANG] "C++ Programming Language, Special Ed" Ch13 Templates, Appendix C.13 Templates
- [CPP TEMP] "C++ Templates: The Complete Guide", 2002
- [EFFECT CPP] "Effective C++, 3Ed"
- [MODERN CPP] "Modern C++ Design", 2001