C++ 模板应用浅析

文章系转载的,原文地址http://blog.csdn.net/cyxisgreat/article/details/37957687,感谢原文作者子程子


把以前写的C++模板的应用心得发表出来。回想起当时在学习C++模板时的无助和恐惧,现在还心有余悸,我分享出来我的心得,只希望别人少走弯路,其实它就这么几种用法,不需要害怕。

我总结了模板的四种使用方式,基本覆盖了大部分的模板使用场景,了解了这四种方式,就可以在看其它代码时理解别人为什么会在这个地方用模板。 


模板的四大场景

1.数据类型与算法相分离的泛型编程
2.类型适配Traits
3.函数转发
4.元编程


1.数据类型与算法相分离的泛型编程

在模板编程中,最常见的就是此类用法,将数据类型与算法相分离,实现泛型编程。
STL本身实现了数据容器与算法的分离,而STL中大量的模板应用,则实现了数据类型与容器算法的分离,它是泛型编程的一个典范。
如:

[cpp]  view plain  copy
  1. std::vector<int>  
  2. std::vector<long>  


单件的模板实现,将单件的算法和单件的类型相分离。
如:

[cpp]  view plain  copy
  1. template <class T> class  Singleton    
  2. {    
  3. protected:    
  4.     Singleton(){}    
  5. public:    
  6.     static T& GetInstance()    
  7.     {    
  8.         static T instance;    
  9.         return instance;    
  10.     }    
  11. };    
  12. Class CMySingleton : public Singleton< CMySingleton >    

数据类型与算法的分离是最容易理解的一种使用场景,我觉得这可能也是发明泛型算法的初衷。

2.类型适配Traits

C++教科书一定会提到C++语言的多态性。我对多态的理解就是:相同的方法产生了不同的行为。这在C++中最常见的用例就是虚函数,虚函数被子类覆盖后由子类重写,不同的子类对于相同的虚函数调用表现出不同的行为,但调用者丝毫不关心具体的实现,它只对于虚接口进行调用完事。这种多态就是运行时的多态。因为它是在运行时才知道最终调用到哪个子类函数上。

与运行时多态相对,另有一种多态形式是借助于模板实现的,模板允许我们使用单一的泛型标记,来关联不同的特定行为:但这种关联是在编译期进行处理的,这些借助于模板的多态称为静多态 。
请看下方的示例。

[cpp]  view plain  copy
  1. class A1    
  2. {      
  3. public:  void fun();   
  4. };      
  5. class A2    
  6. {      
  7. public:  void fun();    
  8. };      
  9. template<typename A>  class CFunInvoker      
  10. {  public:    
  11.     Static void invoke(A* t)    
  12.     {   t->fun();  }    
  13. }      
  14. A1 a1;      
  15. A2 a2;      
  16. CFunInvoker<A1>::invoke(&a1);     
  17. CFunInvoker<A2>::invoke(&a2);  

 
A1,A2两个类,都有一个fun的函数。另一个调用者CFunInvoker需要调用这两个类的fun函数。上面这个例子,A1和A2并没有什么关联,它们只需要提供一个名为fun参数为空的函数就可以被调用了。而调用者CFunInvoker对于被调用者的要求也就是有这样一个函数就行。仅仅能过约定好函数名和参数的方式就可以实现对A1,A2,CFunInvoker  几乎完全的解耦。 

如果用动多态实现的话,那就需要A1和A2继承自同一个含有虚接口fun的父类(比如这个父类叫CFunBase)。并且对于CFunInvoker来说,它需要定义一个这样的父类指针(CFunBase*),并对其进行调用。这个时候,A1和A2就不那么自由了,任何对CFunBase的修改都会影响到A1和A2的功能。这样A1,A2,CFunInvoker的耦合性变高了,它们需要的是一个类来实现关联。

因此,静多态的好处就是:静多态不需要实现多态的类型有公共的基类,因为它可以一定程度上的解耦,但是它仍然需要模板类与模板参数之间有一些协议(这里协议就比如上面的例子中需要名为fun参数为空的函数)。

但如果有些模板参数类型不满足这些协义,怎么办?比如我想调用CFunInvoker<int>::invoke但int类型又提供不了一个名为fun参数为空的函数。

因此我们引入静多态的另一个用处:Traits(粹取)

比如下面这个Host类需要模板参数类型提供一个叫dosomething的方法,所以Host<A>是可以编译通过,但Host<int>是编译不过的

为了解决这个问题,我们增加一个Traits类,它一定会对外提供一个dosomething的方法。对于普通类型,它就转发这个方法,于对int型,它作了特化,实现了一个空的dosomething的方法。因此无论是Host<Traits<A>> 还是Host<Traits<int>>,都可以通过编译

STL中大量运用了traits。比如我们常见的string类型,别以为它只能处理字符串,它可以处理任何类型,你甚至可以用它来处理二进制的buffer(binaryarray)。

比如我们可以修改std::string让其内部处理long类型,让它成为一个long型数组。

[cpp]  view plain  copy
  1. typedef   
  2. basic_string<long, char_traits<long>, allocator<long> > longstring;  
  3.   
  4. longstring strlong;  
  5. strlong.push_back(23);  
  6. strlong.push_back(4562);  
  7. long arrLong[2] = {23, 4562};  
  8.   
  9. longstring strlongFromArr(arrLong, ARRAYSIZE(arrLong));  
  10. assert(strlong == strlongFromArr);  


3.函数转发

模板类的很多应用在于它能针对不同的模板参数生成不同的类。这使得我们可以通过模板类将函数指针以及它的参数类型记录下来,在需要的时候再对函数进行调用。

基于函数转发的应用有很多

  • boost::function
  • boost::signal slot
  • 模板实现的C++委托
  • 模板实现的C++反射
…………


凡是涉及到把函数指针存放起来,进行延迟调用的情况,都可以应用函数转发


下面模拟一个简单的转发

[cpp]  view plain  copy
  1. template<typename T>  class function;  
  2. template<typename R, typename A0>    
  3. class  function <R (A0)>  
  4. {    
  5. public:    
  6.     typedef R(*fun)(A0 );    
  7.     function(fun ptr):m_ptr(ptr){}       
  8.     R operator()(A0 a)    
  9.     {(*m_ptr)(a);}    
  10.     fun m_ptr;    
  11. };    
  12. int testfun(int a)    
  13. {     
  14. printf("%d", a);    
  15.     return 2;  
  16. }   
  17. function<int (int)> f1(&testfun);  
  18. f1(4);  
上面的例子把函数testfun的函数指针,以及它的函数签名int (int)作为模板参数保存在了f1这个对象中。在需要的时候,就可以用f1对这个函数进行调用。


下面的例子模拟了类成员函数的转发

[cpp]  view plain  copy
  1. <pre name="code" class="cpp">template<class T> class function;    
  2. template<typename R, typename A0, typename T>    
  3. class  function<R (T::*)(A0) >     
  4. {    
  5. public:    
  6.     typedef R(T::*fun)(A0);    
  7.      function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}    
  8.     
  9.     R operator()(A0 a)      {(m_pThis->*m_ptr)(a);}    
  10.     fun m_ptr;    
  11.     T* m_pThis;    
  12. };    
  13. class CA  
  14. {  
  15. public:  
  16.     void Fun(int a) {cout << a;}  
  17. };  
  18.   
  19. CA a;  
  20. function<void (CA::*)(int)>   f(&CA::Fun, &a);  
  21. f(4);  // 等价于a.Fun(4);  

 

上面的例子把class CA的对象指针,成员函数指针,以及它的成员函数签名

[cpp]  view plain  copy
  1. void (CA::*)(int)  
作为模板参数保存在了f这个对象中。在需要的时候,就可以用f对这个对象的这个成员函数函数进行调用。

调用的方式很简单

[cpp]  view plain  copy
  1. f(4);  // 等价于a.Fun(4);  
就像是调用一个普通的C函数一样。CA类对象不见了,.或->操作符不见了。函数转发实现了一层层的封装与绑定,最终上调用者与CA类型隔离,实现了解耦。



不过函数转发的这种封装使会使得调用效率降低,如何让封装后的调用像普通函数调用一样快,请参考我发的另一篇学习心得

高效C++委托的原理

4.元编程

许多书介绍元编程是这样说的:Metaprogram:a program about a program。就是“一个关于另一个程序的程序”。这方面介绍很多。从一个示例入手:一段从1累加到100的程序

主模板有一个整形的参数N, 主模板中的枚举值value取值会取得模板参数为N-1的模板类的value值,加上自身的N值。然后为N=1的时候特化处理value=1。这样在GetSum<100>这个类中它的value值就是5050。这个值不是在运行时候计算机算的,而是在编译时编译器已经算好了。这么长的C++代码最终编译出来的结果就和只写一句
[cpp]  view plain  copy
  1. prinft("%d",5050);  

产生的汇编指令是一样的。

从这个小例子可以总结出元编程的思想:
在编译期实现对类型或数值的计算。
利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。


在编译期我们可以用来帮助计算的工具有:
  • 模板的特化
  • 函数重载决议
  • typedef
  • static类型变量和函数
  • sizeof,
  • =,:?,-,+,<, >运算符
  • enum

1。元编程中特化用法

一般用特化实现条件的判断。
包括普通if的判断
循环条件终结判断
。。。。。
下面是一个例子
[cpp]  view plain  copy
  1. struct is_void    
  2. {    
  3.     enum{value = false;}    
  4. }    
  5.     
  6. template<>    
  7. struct is_void<void>    
  8. {    
  9.     enum{value = true;}    
  10. }    
  11.     
  12. std::cout << is_void<int>  //显示false  

上面这个例子可以用来判断一个类型是不是void类型

2。元编程中函数重载决议用法

下面这个例子来自于《C++设计模式新思维》
用来判断两个类型之间是否有转化关系
[cpp]  view plain  copy
  1. <pre name="code" class="cpp">template <class T, class U>    
  2. struct Conversion    
  3. {    
  4.   static char Test(U);    
  5.   static long Test(...);    
  6.   static T MakeT();    
  7.   enum { exists =    
  8.   (sizeof(Test(MakeT())) == sizeof(char) )};    
  9. };    
  10.   
  11.   
  12. class A;  
  13. class B: public A;  
  14.   
  15. printf("%d, %d", Conversion<B*, A*>::exists, Conversion<A*, B*>::exists);  
  16.                     输出1,0  
 
 上面的例子通过重载决议和sizeof取得重载函数Test的返回值大小,再通过枚举常量exists在编译期保存。 
[cpp]  view plain  copy
  1. Conversion<B*, A*>  
中,重载决议采用的是char Test(A*)方法,因此Conversion<B*,A*>::exists为1。

而在
[cpp]  view plain  copy
  1. Conversion<A*, B*>  
中,重载决议采用的是long Test(...)方法,因此Conversion<A*,B*>::exists为0。

3。元编程中typedef用法

在元编程中,typedef主要用来形成编译期的类型数据结构。
最经典的TypeList结构
boost::tuple结构也是基于类似TypeList的结构
Boost的mpl库中还实现了vector map set等数据结构

比如下面的示例就实现了一个ClassA=>ClassB=>ClassC的类型链表。
[cpp]  view plain  copy
  1. typedef  struct   NULL_TYPE{}   NullType    
  2.     
  3. template<typename T,  typename   U = NullType>     
  4. struct Typelist       
  5. {     
  6.  typedef T Head;    
  7.  typedef U Tail;    
  8. }    
  9.     
  10. typedef  Typelist<ClassA,  Typelist< ClassB, Typelist< ClassC,   NullType>>> mytypelist ;   

这个类型链表仅仅只有类型信息,有什么用呢?
我们可以改造一下,给它增加两个对象,形成一个可以把不同类型元素存到一个链表中的对象链表
[cpp]  view plain  copy
  1. template<typename T,  typename   U = NullType>     
  2. struct Typelist       
  3. {     
  4.  typedef T Head;    
  5.  typedef U Tail;    
  6.   
  7. Head m_head;  
  8. Tail  m_tail;  
  9. }    
  10.   
  11. Typelist<ClassA, Typelist <ClassB>> storage;  
  12. Storage. m_head = ClassA();  
  13. Storage.m_tail.m_head = ClassB();  

这样链表就存了ClassA和ClassB的两个实例对象。

下面举一个运用typelist强大威的的实例。

typelist实现简单工厂
很多时候我们会涉及到对象工厂, 这是简单工厂模式的一种,就是根据需求产生不同的类对象。
下面就是一个简单工厂的例子,这种代码随处可见于各种C++项目。
[cpp]  view plain  copy
  1. void * CreateObj(const std::string & strClsName)    
  2. {    
  3.     if (strClsName  == “ClassA")    
  4.     {    
  5.         return new  ClassA();    
  6.     }    
  7.     else if (strClsName  == " ClassB")    
  8.     {    
  9.         return new  ClassB();    
  10.     }    
  11.     else if (strClsName  == " ClassC")    
  12.     {    
  13.         return new  ClassC();    
  14.     }    
  15. }  

这就是一个分支结构,如果类型特别多的话,代码就会很长很挫。
我们可以用typelist来帮我们生成这样的代码。这是一种高大上的方法
[cpp]  view plain  copy
  1. class ClassA  
  2. {     
  3. public;  
  4. virtual const char*  getClassName(){    return  m_classname;}  
  5. static char* m_classname; //每个类型用一个字符串来表示自己的型别  
  6. };  
  7. char* ClassA::m_classname = “ClassA”;  
  8.   
  9.   
  10. class ClassB …  
  11. class ClassC …  
  12.   
  13.   
  14. typedef     
  15.     Typelist<ClassA,     
  16.          Typelist< ClassB,     
  17.             Typelist< ClassC>     
  18.         >    
  19.     >    
  20.     
  21.  mytypelist ;   
  22. template<typename T, typename U>     
  23. struct Typelist       
  24. {     
  25.     typedef T Head;    
  26.     typedef U Tail;      
  27.     
  28.     static void* CreatObj(const char *pName)       
  29.     {     
  30.         if (strcmp(Head::m_classname, pName) == 0 )     
  31.         {     
  32.             return new Head;  //找到对应的类  
  33.         }       
  34.         else       
  35.         {     
  36.             return Tail::CreatObj(pName );//这里就是对Typelist进行了递归调用,从而产生了分支代码     
  37.         }     
  38.     }    
  39. };  
  40.   
  41.   
  42. template<typename T>     
  43. struct Typelist<T, NullType >//特化用以递归结束条件  
  44. {     
  45.     
  46.     static void* CreatObj(const char *pName)       
  47.     {     
  48.         if (strcmp(Head::m_classname, pName) == 0 )     
  49.         {     
  50.             return new Head;    
  51.         }       
  52.         else       
  53.         {     
  54.             return NULL;     
  55.         }     
  56.     }    
  57. };  
  58.   
  59.   
  60. ClassA* pa = (ClassA* )mytypelist:: CreatObj(“ClassA”);  
  61. ClassB* pb = (ClassB* )mytypelist:: CreatObj(“ClassB”);  
  62. ClassC* pc = (ClassC* )mytypelist:: CreatObj(“ClassC”);  
  63.   
  64. …  

这种方式并没有减少最终生成的汇编指令级的if else的数量,但是它不需要我们写那么多的if else了。通过模板的递归方式,让编译器自动为我们生成分支判断。

动态类型创建已经由前面的类工厂实现了,现在我们可以用类似的方面实现动态类型识别
RuntimeClass主要有两方面的功能:
1.动态类型创建   
ClassA* pObj = CreateObj(“ClassA”);
2.动态类型识别
pObj ->IsKindOf( ClassA::GetRuntimeClass() ) ;

在MFC中,IsKindOf 方法是通过遍历继承链来确定是否属于某种类型。一看到这种遍历或循环的方式,我们就可以考虑用模块递归来实现

下面是实现代码。只需利用前面讲到的Conversion模板
[cpp]  view plain  copy
  1. template<typename T,   typename   U = NullType>     
  2. struct Typelist       
  3. {     
  4.  typedef T Head;    
  5.  typedef U Tail;    
  6.     
  7.  template<typename SuperClass>    
  8.  static bool IsKindOf(const char *pName)       
  9.  {     
  10.   if (strcmp(Head:: getClassName(), pName) == 0 )     
  11.   {     
  12.    return Conversion<Head*, SuperClass*>::exists;    
  13.   }       
  14.   else       
  15.   {     
  16.    return Tail::IsKindOf<SuperClass>(pName );     
  17.   }     
  18.  }     
  19. };   
  20.   
  21. class ClassA;  
  22. class ClassB : public Class A;  
  23. class ClassC;  
  24.   
  25. ClassA* pa = new  ClassA;  
  26. ClassB* pb = new  ClassB;  
  27.   
  28.   
  29. typedef   Typelist<ClassA, Typelist< ClassB,  Typelist< ClassC>> >    
  30.  mytypelist ;    
  31.   
  32. printf(“%d, %d, %d,%d”, mytypelist::IsKindOf< ClassA >(pa->getClassName()),mytypelist::IsKindOf< ClassB >(pa->getClassName()),  
  33.   mytypelist::IsKindOf< ClassC >(pa->getClassName()),  
  34.   mytypelist::IsKindOf< ClassA >(pb->getClassName()));  
  35.                                        // 结果是 1,0,0,1  


元编程技术很多,比如还有数值运算等(最简单的1到100累加的例子),我这里只是挂一漏万,具体可以参考《C++设计模式新思维》。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值