C++ 範本應用淺析

來源:互聯網
上載者:User

標籤:常見   turn   version   彙編指令   重載函數   cpp   microsoft   c函數   cout   

把曾經寫的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.類型適配TraitsC++教科書一定會提到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>是編譯只是的

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3l4aXNncmVhdA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" style="font-size:18px">

為瞭解決問題。我們添加一個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++設計模式新思維》。


C++ 範本應用淺析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.