zoukankan      html  css  js  c++  java
  • C++ 模板应用浅析

    把曾经写的C++模板的应用心得发表出来。

    回忆起当时在学习C++模板时的无助和恐惧,如今还心有余悸。我分享出来我的心得,仅仅希望别人少走弯路,事实上它就这么几种使用方法,不须要害怕。

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

     


    模板的四大场景

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


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

    在模板编程中,最常见的就是此类使用方法。将数据类型与算法相分离,实现泛型编程。


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

    std::vector<int>
    std::vector<long>


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

    template <class T> class  Singleton  
    {  
    protected:  
        Singleton(){}  
    public:  
        static T& GetInstance()  
        {  
            static T instance;  
            return instance;  
        }  
    };  
    Class CMySingleton : public Singleton< CMySingleton >  

    数据类型与算法的分离是最easy理解的一种使用场景。我认为这可能也是发明泛型算法的初衷。


    2.类型适配Traits

    C++教科书一定会提到C++语言的多态性。我对多态的理解就是:同样的方法产生了不同的行为。这在C++中最常见的用例就是虚函数,虚函数被子类覆盖后由子类重写,不同的子类对于同样的虚函数调用表现出不同的行为,但调用者丝毫不关心详细的实现,它仅仅对于虚接口进行调用完事。

    这样的多态就是执行时的多态。由于它是在执行时才知道终于调用到哪个子类函数上。


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

    class A1  
    {    
    public:  void fun(); 
    };    
    class A2  
    {    
    public:  void fun();  
    };    
    template<typename A>  class CFunInvoker    
    {  public:  
    	Static void invoke(A* t)  
    	{   t->fun();  }  
    }    
    A1 a1;    
    A2 a2;    
    CFunInvoker<A1>::invoke(&a1);   
    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型数组。

    typedef 
    basic_string<long, char_traits<long>, allocator<long> > longstring;
    
    longstring strlong;
    strlong.push_back(23);
    strlong.push_back(4562);
    long arrLong[2] = {23, 4562};
    
    longstring strlongFromArr(arrLong, ARRAYSIZE(arrLong));
    assert(strlong == strlongFromArr);
    


    3.函数转发

    模板类的非常多应用在于它能针对不同的模板參数生成不同的类。

    这使得我们能够通过模板类将函数指针以及它的參数类型记录下来。在须要的时候再对函数进行调用。

    基于函数转发的应用有非常多

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


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


    以下模拟一个简单的转发

    template<typename T>  class function;
    template<typename R, typename A0>  
    class  function <R (A0)>
    {  
    public:  
        typedef R(*fun)(A0 );  
        function(fun ptr):m_ptr(ptr){}     
        R operator()(A0 a)  
        {(*m_ptr)(a);}  
        fun m_ptr;  
    };  
    int testfun(int a)  
    {   
    printf("%d", a);  
        return 2;
    } 
    function<int (int)> f1(&testfun);
    f1(4);
    
    上面的样例把函数testfun的函数指针,以及它的函数签名int (int)作为模板參数保存在了f1这个对象中。在须要的时候,就能够用f1对这个函数进行调用。


    以下的样例模拟了类成员函数的转发

    <pre name="code" class="cpp">template<class T> class function;  
    template<typename R, typename A0, typename T>  
    class  function<R (T::*)(A0) >   
    {  
    public:  
        typedef R(T::*fun)(A0);  
         function (fun p, T* pthis):m_ptr(p), m_pThis(pthis){}  
      
        R operator()(A0 a)      {(m_pThis->*m_ptr)(a);}  
        fun m_ptr;  
        T* m_pThis;  
    };  
    class CA
    {
    public:
    	void Fun(int a) {cout << a;}
    };
    
    CA a;
    function<void (CA::*)(int)>   f(&CA::Fun, &a);
    f(4);  // 等价于a.Fun(4);

    
    

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

    void (CA::*)(int)
    作为模板參数保存在了f这个对象中。在须要的时候,就能够用f对这个对象的这个成员函数函数进行调用。

    调用的方式非常easy

    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++代码终于编译出来的结果就和仅仅写一句

    prinft("%d",5050);

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

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



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

    1。元编程中特化使用方法

    一般用特化实现条件的推断。
    包含普通if的推断
    循环条件终结推断

    。。


    以下是一个样例
    struct is_void  
    {  
        enum{value = false;}  
    }  
      
    template<>  
    struct is_void<void>  
    {  
        enum{value = true;}  
    }  
      
    std::cout << is_void<int>  //显示false
    

    上面这个样例能够用来推断一个类型是不是void类型

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

    以下这个样例来自于《C++设计模式新思维》
    用来推断两个类型之间是否有转化关系
    <pre name="code" class="cpp">template <class T, class U>  
    struct Conversion  
    {  
      static char Test(U);  
      static long Test(...);  
      static T MakeT();  
      enum { exists =  
      (sizeof(Test(MakeT())) == sizeof(char) )};  
    };  
    
    
    class A;
    class B: public A;
    
    printf("%d, %d", Conversion<B*, A*>::exists, Conversion<A*, B*>::exists);
                        输出1,0
     
    
    上面的样例通过重载决议和sizeof取得重载函数Test的返回值大小,再通过枚举常量exists在编译期保存。
    Conversion<B*, A*>
    中,重载决议採用的是char Test(A*)方法,因此Conversion<B*,A*>::exists为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的类型链表。
    typedef  struct   NULL_TYPE{}   NullType  
      
    template<typename T,  typename   U = NullType>   
    struct Typelist     
    {   
     typedef T Head;  
     typedef U Tail;  
    }  
      
    typedef  Typelist<ClassA,  Typelist< ClassB, Typelist< ClassC,   NullType>>> mytypelist ; 

    这个类型链表只唯独类型信息,有什么用呢?
    我们能够改造一下,给它添加两个对象,形成一个能够把不同类型元素存到一个链表中的对象链表
    template<typename T,  typename   U = NullType>   
    struct Typelist     
    {   
     typedef T Head;  
     typedef U Tail;  
    
    Head m_head;
    Tail  m_tail;
    }  
    
    Typelist<ClassA, Typelist <ClassB>> storage;
    Storage. m_head = ClassA();
    Storage.m_tail.m_head = ClassB();

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

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

    typelist实现简单工厂
    非常多时候我们会涉及到对象工厂, 这是简单工厂模式的一种,就是依据需求产生不同的类对象。
    以下就是一个简单工厂的样例。这样的代码随处可见于各种C++项目。
    void * CreateObj(const std::string & strClsName)  
    {  
        if (strClsName  == “ClassA")  
        {  
            return new  ClassA();  
        }  
        else if (strClsName  == " ClassB")  
        {  
            return new  ClassB();  
        }  
        else if (strClsName  == " ClassC")  
        {  
            return new  ClassC();  
        }  
    }

    这就是一个分支结构,假设类型特别多的话,代码就会非常长非常挫。
    我们能够用typelist来帮我们生成这种代码。

    这是一种高大上的方法

    class ClassA
    {   
    public;
    virtual const char*  getClassName(){    return  m_classname;}
    static char* m_classname; //每一个类型用一个字符串来表示自己的型别
    };
    char* ClassA::m_classname = “ClassA”;
    
    
    class ClassB …
    class ClassC …
    
    
    typedef   
        Typelist<ClassA,   
             Typelist< ClassB,   
                Typelist< ClassC>   
            >  
        >  
      
     mytypelist ; 
    template<typename T, typename U>   
    struct Typelist     
    {   
        typedef T Head;  
        typedef U Tail;    
      
        static void* CreatObj(const char *pName)     
        {   
            if (strcmp(Head::m_classname, pName) == 0 )   
            {   
                return new Head;  //找到相应的类
            }     
            else     
            {   
                return Tail::CreatObj(pName );//这里就是对Typelist进行了递归调用。从而产生了分支代码   
            }   
        }  
    };
    
    
    template<typename T>   
    struct Typelist<T, NullType >//特化用以递归结束条件
    {   
      
        static void* CreatObj(const char *pName)     
        {   
            if (strcmp(Head::m_classname, pName) == 0 )   
            {   
                return new Head;  
            }     
            else     
            {   
                return NULL;   
            }   
        }  
    };
    
    
    ClassA* pa = (ClassA* )mytypelist:: CreatObj(“ClassA”);
    ClassB* pb = (ClassB* )mytypelist:: CreatObj(“ClassB”);
    ClassC* pc = (ClassC* )mytypelist:: CreatObj(“ClassC”);
    
    …
    

    这样的方式并没有降低终于生成的汇编指令级的if else的数量。可是它不须要我们写那么多的if else了。通过模板的递归方式,让编译器自己主动为我们生成分支推断。

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

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

    以下是实现代码。仅仅需利用前面讲到的Conversion模板
    template<typename T,   typename   U = NullType>   
    struct Typelist     
    {   
     typedef T Head;  
     typedef U Tail;  
      
     template<typename SuperClass>  
     static bool IsKindOf(const char *pName)     
     {   
      if (strcmp(Head:: getClassName(), pName) == 0 )   
      {   
       return Conversion<Head*, SuperClass*>::exists;  
      }     
      else     
      {   
       return Tail::IsKindOf<SuperClass>(pName );   
      }   
     }   
    }; 
    
    class ClassA;
    class ClassB : public Class A;
    class ClassC;
    
    ClassA* pa = new  ClassA;
    ClassB* pb = new  ClassB;
    
    
    typedef   Typelist<ClassA, Typelist< ClassB,  Typelist< ClassC>> >  
     mytypelist ;  
    
    printf(“%d, %d, %d,%d”, mytypelist::IsKindOf< ClassA >(pa->getClassName()),mytypelist::IsKindOf< ClassB >(pa->getClassName()),
      mytypelist::IsKindOf< ClassC >(pa->getClassName()),
      mytypelist::IsKindOf< ClassA >(pb->getClassName()));
                                           // 结果是 1,0,0,1
    
    
    
    


    元编程技术非常多。比方还有数值运算等(最简单的1到100累加的样例),我这里仅仅是挂一漏万。详细能够參考《C++设计模式新思维》。


  • 相关阅读:
    冒泡排序算法分析和实现
    选择排序算法分析与实现
    nio和 bio
    TCP三次握手
    IE input X 去掉文本框的叉叉和密码输入框的眼睛图标
    <%#eval() %>和<%#bind() %> 的区别
    <%#Eval() %>的常用方法
    C#(ASP.net)从其他网站抓取内容并截取有用信息
    JQuery写的一个简单的分页插件-2
    简单实用的jQuery分页插件
  • 原文地址:https://www.cnblogs.com/liguangsunls/p/7247006.html
Copyright © 2011-2022 走看看