C++ 模板慣用法


原文鏈接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 的函數

  1. template <size_t BufSize, class CharT>  
  2. inline  
  3. std::basic_string<CharT> make_string(const CharT* format, ...)  
  4. {  
  5.     CharT buf[BufSize];  
  6.     va_list args;  
  7.   
  8.     va_start(args, format);  
  9.     // vsprintf 是函數模板, 其針對 char 特化調用 vsprintf_s, 針對 wchar_t 特化調用 vswprintf_s  
  10.     vsprintf(buf, BufSize, format, args);  
  11.     va_end(args);  
  12.   
  13.     // 注意: 返回時構造可讓 VC 編譯優化為只有一次 string ctor 調用, 沒有額外 copy  
  14.     return std::basic_string<CharT>(buf);  
  15. }  

編譯優化的開關^

bool 模板參數,或整數模板參數 + 閾值,避免重復代碼時借助編譯優化

示例:一個支持透明色的 32bit blit 函數

  1. template <bool UseMask>  
  2. void blit(int* dst, const int* src, int mask, size_t size)  
  3. {  
  4.     for (size_t i = 0; i < size; i++, dst++, src++) {  
  5.         if (!UseMask || *src != mask)  
  6.             *dst = *src;  
  7.     }  
  8. }  

推導數組元素個數^

可由參數推導求出數組的元素個數,要求必須是數組名,而非指向數組的指針或 new[] 數組

示例:VC CRT 的 _countof 計算數組的元素個數

  1. // 以 C++ 方式編譯時, _countof 的定義如下  
  2. template <typename _CountofType, size_t _SizeOfArray>  
  3. char (*__countof_helper(UNALIGNED _CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];  
  4. #define _countof(_Array) (sizeof(*__countof_helper(_Array)) + 0)  

示例:多數 VC CRT buffer 操作函數都有 Secure Template Overloads 版本

  1. template <size_t size>  
  2. errno_t strcpy_s(char (&strDestination)[size], const char *strSource);  

推導常數^

示例:掩碼常數 Mask<N>::Value

  1. #define _MASK_VAL(x)    (1 << x)  
  2. // 用於代替上面的宏  
  3. // 實例化超出 [0:31] 范圍的 Mask 時, 產生編譯警告 warning C4293  
  4. template <unsigned int N>  
  5. struct Mask {  
  6.     enum { Value = (1 << N) };  
  7. };  

示例:GCD<N, M>::Value 求解最大公約數

  1. // GCD<N, M> 原始模板  
  2. template <unsigned int N, unsigned int M>  
  3. struct GCD {  
  4.     static const unsigned int Value = GCD<M, N % M>::Value;  
  5. };  
  6.   
  7. // GCD<N, 0> 特化, 用以終止遞歸條件  
  8. template <unsigned int N>  
  9. struct GCD<N, 0> {  
  10.     static const unsigned int Value = N;  
  11. };  

隱式轉換的顯式函數 implicit_cast^

見 [CPP LANG] 13.3.1 模板函數參數推導

  1. 因為是 return u,而不是 return (T) u,所以是隱式轉換
  2. 可推導的參數放到 template 參數列表的最后
  3. 效率:有兩次拷貝(參數、返回值),但通常編譯優化可將其減小到一次拷貝
  1. template <class T, class U> T implicit_cast(U u) { return u; }  
  2.   
  3. void func(int i)  
  4. {  
  5.     implicit_cast<double>(i);       // T 顯式指定為 double, U 由參數推導得出 int  
  6.     implicit_cast<chardouble>(i); // i 先轉換為 double, 再隱式轉換為 char  
  7.     implicit_cast<char*>(i);        // 錯誤: int 不能隱式轉換為 char*  
  8. }  

推導 callable 可調用物^

  1. 基於函數指針類型

    • 可提取 callable 的參數和返回值類型
    • callable 只接受函數指針
    1. template <class RetT, class ArgT>  
    2. bool calc_and_check_1(RetT (*calc)(ArgT), ArgT arg, bool (*check)(RetT))  
    3. {  
    4.     const RetT& ret = calc(arg);  
    5.     return check(ret);  
    6. }  
  2. 基於直接的類型

    • callable 接受函數指針、函數對象、成員函數
    • std 算法使用這種方法

    提取 callable 的參數和返回值類型時,callable 只能是函數對象:

    • 函數對象類:含 typedef 指明參數和返回值類型,std 約定命名為 [first_|second_]argument_type, result_type,可從 binary_function, unary_function 繼承
    • 函數指針:用 std::ptr_fun 轉換為函數對象
    • 成員函數:用 std::mem_fun, tr1::bind 轉換為函數對象
    1. template <class Calc, class ArgT, class Check>  
    2. bool calc_and_check_2(Calc calc, ArgT arg, Check check)  
    3. {  
    4.     const Calc::result_type& ret = calc(arg);  
    5.     return check(ret);  
    6. }  
  3. 使用 tr1::function 或 boost::function

    • 和第 2 種方法類似,但是 callable 的參數和返回值類型是固定的
    • tr1::function 已放入 std 名字空間
    1. bool calc_and_check_3(std::function<int (double)> calc, double arg, std::function<bool (int)> check)  
    2. {  
    3.     const std::function<int (double)>::result_type& ret = calc(arg);  
    4.     return check(ret);  
    5. }  

用成員模板實現繼承隱喻^

見 [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>

  1. template <class Type>  
  2. class auto_ptr {  
  3.     // 非模板的 copy ctor, assign 無法從下面的模板版生成, 需要顯式寫出, 否則將使用編譯器生成的  
  4.     auto_ptr(auto_ptr& right) throw() : ptr_(right.release()) {}  
  5.   
  6.     // 模板的 copy ctor: 當  T2* => Type* 時, 隱喻 auto_ptr<T2> => auto_ptr<Type>  
  7.     // assign 和 operator auto_ptr<T2> 的模板版相似從略  
  8.     template <class T2>  
  9.     auto_ptr(auto_ptr<T2>& right) throw() : ptr_(right.release()) {}  
  10.   
  11.     Type* release() throw() {  
  12.         Type* temp = ptr_;  
  13.         ptr_ = 0;  
  14.         return temp;  
  15.     }  
  16.   
  17. private:  
  18.     Type*   ptr_;  
  19. };  

假設模板基類中的成員^

模板會小小的違背繼承規則:Derived<T> 不能訪問 Base<T> 的成員,除非告訴編譯器假設它存在,即推遲檢查到第二階段編譯,見 [EFFECT CPP] Item 43 [CPP TEMP] 9.4.2

假設 Base<T> 的成員 member 存在的方法有:

  1. 用 this->member
  2. 用 using Base<T>::member 導入名字
  3. 用 Base<T>::member,副作用是關閉 virtual member 的動態綁定

CRTP 循環模板模式^

見 [CPP TEMP] 16.3 CRTP: Curiously Recurring Template Pattern

示例:使用類專屬的 set_new_handler 和 operator new,見 [EFFECT CPP] Item 49

  1. template <class T>  
  2. class NewHandlerSupport;    // 含針對類型 T 的 set_new_handler 和 operator new  
  3.   
  4. class Widget : public NewHandlerSupport<Widget> {};  

結合使用函數模板和類模板^

  1. 類模板能以函數對象類的形式保存調用環境,並有模板默認參數,但不能推導參數
  2. 函數模板能推導參數,但不能保存環境和有模板默認參數

兩者結合后,用來寫生成函數對象的工廠函數

std 案例:用於構造謂詞的 binder, adapter 和 negater,見 [CPP LANG] 18.4.4

特化的基本目的^

  1. 解決對於特定類型實例化時的語義錯誤

    示例:數組字符串的比較

    1. // 原始模板使用值比較  
    2. template <class T>  
    3. bool less(T l, T r)  
    4. {  
    5.     return l < r;  
    6. }  
    7.   
    8. // const char* 應該用 strcmp 比較  
    9. template <>  
    10. bool less(const char* l, const char* r)  
    11. {  
    12.     return strcmp(l, r) < 0;  
    13. }  
  2. 為特定類型實例化提供更高效的實現

    示例:交換 STL 容器 string, vector 等

    1. // 原始模板使用值交換  
    2. template <class T>  
    3. void swap(T& l, T& r)  
    4. {  
    5.     T t(l);  
    6.     l = r;  
    7.     r = t;  
    8. }  
    9.   
    10. // std::string 最好用 swap 成員函數, 以發揮 pimpl 方式實現(假設)的效能  
    11. template <class CharT>  
    12. void swap(std::basic_string<CharT>& l, std::basic_string<CharT>& r)  
    13. {  
    14.     l.swap(r);  
    15. }  
  3. 主要依靠特化工作,反而原始模板的意義為次,traits 通常使用這種方法

    示例:編譯時 assert 斷言

    1. // 靜態/編譯時 assert 斷言, 要求 expr 能在編譯時求值  
    2. // 如果 expr = false, 產生編譯錯誤 error C2027  
    3. template <bool expr> struct StaticAssert;  
    4. template <> struct StaticAssert<true> {};  
    5. template <size_t size> struct StaticAssertTest {};  
    6.   
    7. #define STATIC_ASSERT(x)    \  
    8.     typedef StaticAssertTest<sizeof(StaticAssert<bool(x)>)>     StaticAssertType##__LINE__  

解決實例化的代碼膨脹^

用提取共性的方法解決實例化產生的目標代碼膨脹 (code bloat),見 [EFFECT CPP] Item 44

示例:部分特化轉接調用完全特化解決代碼膨脹,見 [CPP LANG] 13.5

  1. // 原始模板  
  2. template <class T>  
  3. class Vector;           // 含最一般的 [] 操作  
  4.   
  5. // Vector<void*> 是針對 void* 的完全特化  
  6. template <>  
  7. class Vector<void*>;    // 含針對 void* 的 [] 操作  
  8.   
  9. // Vector<T*> 是針對任意類型 T 指針的部分特化  
  10. template <class T>  
  11. class Vector<T*> : private Vector<void*> {  
  12.     typedef Vector<void*> Base;  
  13.   
  14.     // 轉接調用完全特化 Vector<void*> 的 [] 操作  
  15.     T*& operator[](int i) {  
  16.         return reinterpret_cast<T*&>(Base::operator[](i));  
  17.     }  
  18. };  
  19.   
  20. // 部分特化 Vector<T*> 的實例化  
  21. // 代碼空間開銷: 所有的 Vector<T*> 實際共享一個 Vector<void*> 實現  
  22. Vector<Shape*>  vps;    // <T*> 是 <Shape*>, T 是 Shape  
  23. Vector<int**>   vppi;   // <T*> 是 <int**>, T 是 int*  

traits 特征和 policy 策略^

見 [CPP TEMP] 15 [CPP LANG] 13.4 [MODERN CPP] almost all book

traits 和 policy 使用相同的下層技術,只是在設計目的和作用上不同

  1. traits 的作用傾向於:特征提取,特征主要指類型和標識,以含 typedef, enum, static 著稱,經常是空類

    traits 的 std 案例:std::iterator_traits,用法舉例:根據 iterator 特征選擇算法,如只對 random_access_iterator_tag 使用快速排序

  2. 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

  1. struct MyTraitsBase {  
  2.     enum {  
  3.         TYPE_UNKNOWN,  
  4.         TYPE_A,  
  5.         TYPE_B  
  6.     };  
  7. };  
  8.   
  9. template <class Type>  
  10. struct MyTraits : public MyTraitsBase {  
  11.     static const int TYPE_ID = TYPE_UNKNOWN;  
  12. };  
  13.   
  14. template <>  
  15. struct MyTraits<TypeA> : public MyTraitsBase {  
  16.     static const int TYPE_ID = TYPE_A;  
  17. };  
  18.   
  19. template <>  
  20. struct MyTraits<TypeB> : public MyTraitsBase {  
  21.     static const int TYPE_ID = TYPE_B;  
  22. };  
  23.   
  24. template <class Type>  
  25. void user(const Type& obj)  
  26. {  
  27.     // 實際中可用表驅動法替代判斷語句  
  28.     // 開啟編譯優化后, 這里的條件分支被優化掉  
  29.     if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_A)  
  30.         // 針對 TypeA 類型的處理  
  31.     else if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_B)  
  32.         // 針對 TypeB 類型的處理  
  33.     else if (MyTraits<Type>::TYPE_ID == MyTraitsBase::TYPE_UNKNOWN)  
  34.         //  針對一般類型的處理  
  35. }  

參考書籍^

    • [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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM