zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    创建FLASK,同步docker
    FLASK Buleprint
    restful api
    Angular JS
    线程日志
    将项目部署到linux下的docker容器中
    安装和卸载docker
    学习目录总编
    Ansible
    装饰器
  • 原文地址:https://www.cnblogs.com/wangkangluo1/p/2783877.html
Copyright © 2011-2022 走看看