程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++模板的特化

C++模板的特化

編輯:C++入門知識

對模板特化的理解: 特化整體上分為全特化和偏特化,這一點大家都沒有什麼置疑,但是細分它們各包括哪幾種狀態就很難界定了,而且很多權威的書上都不一致,管它呢,反正我們能會用各種特化就可以了。 下面就談談我個人對特化的劃分和定義: 所謂特化,就是將泛型的東東搞得具體化一些,從字面上來解釋,就是為已有的模板參數進行一些使其特殊化的指定,使得以前不受任何約束的模板參數,或受到特定的修飾(例如const或者搖身一變成為了指針之類的東東,甚至是經過別的模板類包裝之後的模板類型)或完全被指定了下來。   這是網上某個人的一些看法: 模板有兩種特化,全特化和偏特化(局部特化) 模板函數只能全特化,沒有偏特化(以後可能有)。 模板類是可以全特化和偏特化的。 全特化,就是模板中模板參數全被指定為確定的類型。 全特化也就是定義了一個全新的類型,全特化的類中的函數可以與模板類不一樣。 偏特化,就是模板中的模板參數沒有被全部確定,需要編譯器在編譯時進行確定。 在類型上加上const、&、*( cosnt int、int&、int*、等等)並沒有產生新的類型。只是類型被修飾了。模板在編譯時,可以得到這些修飾信息。   我個人也比較贊同這位仁兄的劃分,全特化的標志就是產生出完全確定的東西,而不是還需要在編譯期間去搜尋適合的特化實現,貌似在我的這種理解下,全特化的東西不論是類還是函數都有這樣的特點,template <>然後是完全和模板類型沒有一點關系的類實現或者函數定義,如果你要說,都完全確定下來了,那還搞什麼模板呀,直接定義不就完事了?但是很多時候,我們既需要一個模板能應對各種情形,又需要它對於某個特定的類型(比如bool)有著特別的處理,這中情形下就是需要的了。   既然剛才提到了全特化的標志,那麼再說說其他一些共性的東西: 一個特化的模板類的標志:在定義類實現時加上了<>,比如class A<int, T>;而在定義一個模板類的時候,class A後面是沒有<>的 全特化的標志:template <>然後是完全和模板類型沒有一點關系的類實現或者函數定義 偏特化的標志:template <typename T.....>,就是說還剩下點東西,不像全特化<>整得那麼徹底     首先推薦兩個不錯的網址: http://www.cnblogs.com/cutepig/archive/2009/02/12/1389479.html http://read.newbooks.com.cn/info/175115.html   先說類模板的特化吧: 誰都沒的說的全特化: // general version template<class T> class Compare { public:     static bool IsEqual(const T& lh, const T& rh)     {         return lh == rh;     } };   // specialize for float template<> class Compare<float> { public:     static bool IsEqual(const float& lh, const float& rh)     {         return abs(lh - rh) < 10e-3;     } }; 誰都沒的說的偏特化: template<class T1, class T2> class A { }   template<class T1> class A<T1, int> { }  接下來的特化種類,到底劃歸到全特化還是偏特化,你自己看著辦吧,不過大致就以下這些了,逃不出我們的手掌心了: 特化為引用,指針類型: // specialize for T* template<class T> class Compare<T*> { public:     static bool IsEqual(const T* lh, const T* rh)     {         return Compare<T>::IsEqual(*lh, *rh);     } }; 特化為另外一個類模板: // specialize for vector<T> template<class T> class Compare<vector<T> > { public:     static bool IsEqual(const vector<T>& lh, const vector<T>& rh)     {         if(lh.size() != rh.size()) return false;         else         {             for(int i = 0; i < lh.size(); ++i)             {                 if(lh[i] != rh[i]) return false;             }         }         return true;     } }; 混合型的: template<typename T1, typename T2> class X {...};  template<typename T> class X<vector<T>, int&> {...}; //至於這裡怎麼都把T2搞沒了變成只依賴一個模板參數T了的問題,大家別著急,我來告訴你個本質的東西,把我這麼三點就可以了:1.模板參數個數一致;2.只要template <...>裡面有東西不是<>,比如typename T,那麼特化時就得用到T;3.不進行任何對模板參數的修飾也是不行的,比如template<typename T> class<T>{...},至少你也得搞個const T之類的吧,呵呵。下面是我搞出來的幾種特殊情況,它們都是正確的: template<typename T1, typename T2> class X {}; template<typename T> class X<vector<T>, T&> {}; template<typename T> class X<vector<T>, int&> {}; template<> class X<vector<double>, int&> {}; template<typename T1, typename T2, typename T3> class X<map<T1,T2>, T3&> {}; 最後,還有一種超級牛X的,在tr1裡面用以實現function的,以前我都沒見過還可以這麼玩的: template<typename T> class Y;//這是在聲明一個類模板,既然聲明了,以後就得按這個規矩來,在我們之前的編程經驗裡,可以重復聲明一個東西沒問題,但是為同一個東東重復聲明出不同的東西就不可以了,因此你就不能再聲明諸如template<typename T1, typename T2> class Y;這樣的聲明了;其實沒有什麼是不能聲明的,既然我們可以聲明變量,聲明函數,聲明類,那麼當然我們也可以聲明函數模板或者類模板的。 template<typename R, typename P1, typename P2> class Y<R (P1, P2)> {...};//針對帶兩個參數,有返回值的函數類型特化,這裡R (P1,P2)是定義了一種類型,該類型是一個隱式的函數指針,返回R,參數為P1和P2,這種對函數指針的定義完全等同於R (*)(P1,P2),但是前一種定義很不常見,大家一般是不會注意到這個地方的。   好了,說了不少關於類模板的特化了,下面再簡要說說函數模板的特化: 函數模板的特化只能是全特化,而不能是偏特化,因此對於函數的特化就比較簡單了,就是重新搞一遍就可以了,舉幾個例子如下: template <class T> T mymax(const T t1, const T t2) {    return t1 < t2 ? t2 : t1; }   template <> const char* mymax(const char* t1,const char* t2) {    return (strcmp(t1,t2) < 0) ? t2 : t1; }   但是你不能這麼搞: template <> bool mymax(const char* t1,const char* t2) {    return (strcmp(t1,t2) < 0); } 其實對於mymax這個模板函數的定義而言,是用一個模板參數控制了三個地方,那麼你在特化的時候,就也需要用一個特定的類型修改那三處相應的地方,如果你非要返回bool,那麼你只能再定義一個函數模板了: template <class T> bool mymax(const T t1, const T t2) {    return t1 < t2 ? t2 : t1; } 問題又來了,大家都知道函數重載是不關心返回值的,而只關心參數個數以及類型是否不一致,不一致就是重載,但是對於模板函數而言,這個規矩不再成立,因為任何與模板相關的東西都只是個架子放在那裡而已,只要它符合語法規則就可以了,這些架子只是在有人要調用它們時才會發揮效力,也就是說,在編譯的時候會為你搜尋合適的模板函數或者類,只要能找到就ok了,而且還要求是只找到一個,要是找到多個也不行,呵呵。   其實,對於函數而言,雖然不能偏特化,即不能再在函數名字後面像模板類一樣搞個<typename T>出來,但是可以通過函數的重載(注意這裡說的重載是指的模板重載,而不是普通意義的函數重載)變通的實現偏特化: template <typename T1, typename T2> bool mymax(T1 t1, T2 t2) {    return t1 < t2 ? t2 : t1; }   template <typename T1> bool mymax(T1 t1, int t2) {    return t1 < t2 ? t2 : t1; }   再談談函數模板參數的推導,大致有以下幾種方法,但是不管怎麼推導,都必須得保證在調用函數前能確定模板函數的各個模板參數的類型。 template <typename T1, typename T2> T2 fun(T1 arg1, int arg2) {     T2 t2;     return t2; } 對於上面這種比較特殊的模板函數,你不能通過傳遞參數來自動得到所有模板參數的類型,因此你必須顯示的指定T1和T2的類型,有兩種方法可以實現此目的: int (*pfun)(double,int) = fun;//借用函數指針定義 cout<<pfun(12.2,11)<<endl; cout<<fun<int,double>(11, 3.2)<<endl;//直接指定類型 如果上述模板函數改為: template <typename T1, typename T2> T2 fun(T1 arg1, T2 arg2) {     return arg2; } 那麼除了上述兩種指定模板參數類型的方法之外,由於該模板函數參數的類型都可以借由其參數獲得,因此我們省去指定模板參數這一步驟,而直接調用該模板函數: fun(23, 2.3);   最後,再談談非類型模板參數的問題,在《C++ Template》的第四章有介紹。 template<typename T, int LEN> struct stack {...}; template<int margin> int add(int x){return x+margin;} 上面兩個例子分別對應了類和函數兩種情形,有人說非類型的模板參數存在得毫無價值,實則不然,因為我們可以借由一個確定的數值來產生一種新的類型或者新的函數。對於上面兩個例子,我覺得用非類型模板參數就很有意義,分別實現了讓用戶指定stack的大小以及指定需要增加的邊際值,關於更多這方面的應用,大家可以在今後的開發過程中逐步發掘,此外,還很有必要強調一下對非類型模板參數的限制,不能使用浮點數、class類型的對象和內部鏈接對象(例如字符串常量"hello world!")作為實參;它們可以是常整數(包括枚舉值)或者指向外部鏈接對象的指針。 對外部鏈接對象的指針舉個例子:   template <char const* name>   class MyClass {...};   extern char const s[] = ”hello”;   MyClass<s> x;       //OK     好了,模板這塊內容我先將這麼多。   又從網上搞到點好東東,也貼在這裡吧:   類模板:   * 如果類模板中含有靜態成員,那麼用來實例化的每種類型,都會實例化這些靜態成員。   * 兩個靠在一起的模板尖括號( > ) 之間需要留個空格,否則,編譯器將會認為是在使用operator>>,導致語法錯誤。   * 特化的實現可以和基本類模板的實現完全不同。   * 類模板可以為模板參數定義缺省值,稱為缺省模板實參,並且他們還可以引用之前的模板參數。   * 成員函數模版不能被聲明為虛函數。   * 類模板不能和另外一個實體共享一個名稱。   eg:         1         int C; 2  3         class C;    // ok,     4  5         int X; 6  7         template < typename T > 8  9         class X;    // error. 和變量X沖突    非類型模板參數:   在編譯期或鏈接期可以確定的常值。這種參數的類型必須是下面的一種:   a> 整型或枚舉   b> 指針類型( 普通對象的指針,函數指針,成員指針 )   c> 引用類型( 指向對象或者指向函數的引用 )   其他的類型目前都不允許作為非類型模板參數使用     今天又突然挖掘出來點好東東,貼在這裡: template <typename t> void f(t t) {} //f1   template <> void f(int t) {} //f2   void f(int t) {} //f3   void f(char t) {} //f4   f(3); //invoke f3 f('3'); //invoke f4   /** caveat: f3 must be put after f2, or an error occurs: specialization of void f(T) [with T = int] after instantiation; notes: the compiler will use f3 as the instantiation for f1, and use f2 as the specialization for f1; rule: specialization must be before instantiation (*);   Above we have discuss the template function, and then we'll focus on member template function.   acronym: MTF(member template function); Firstly, you should pay attention to the rule: the specialization of MTF must be the outside of the class, i.e., inline should not be allowed.   Secondly, specialization and instantiation still follow the rule (*). But you'd better put the instantiation outside of the class since specialization must be the outside of the class(the root cause is: if you wanna specialize a MTF, you should give compiler the defenition of the templcate firstly. But if you use the specialization as the inline method, the specialization will be anxious since you can put the defination of MTF outside of the class. As you know, the inline member function precedes the non-inline member function. So the compiler will chose the safest way to solve it, i.e., the specialization must be put outside of class declaration). */   

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved