一、模板具體化:
函數模板是通用的函數描述,也就是說,它們使用泛型來定義函數,其中的泛型可用具體的類型(如int或double)替換。通過將類型作為參數傳遞給模板,可使編譯器生成該類型的函數。由於模板允許以泛型(而不是具體類型)的方式編寫程序,因此有時也被稱為通用編程。由於類型是用參數表示的,因此模板特性有時也被稱為參數化類型(parameterized types)。
創建模板,關鍵字template和typename是必需的,除非可以使用關鍵字class代替typename。另外,必須使用尖括號。如下程序所示:
template <typename T> void swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; }
注意:模板並非函數定義,但使用了int的模板實例是函數定義。
(1)隱式實例化:
最初,編譯器只能通過隱式實例化,來使用模板生成函數定義,這也是我們最常用的方法;如可以像下面這樣使用上面定義的函數模板:
short a, b; swap(a, b); // T 為 short 類型 int c, d; swap(c, d); // T 為 int 類型
使用上面的例子程序,我們可以交換兩個同類型(int,double……)的值,當如果T為數組、指針或者結構,那么編寫的模板函數就無法處理這些類型了,一種方案是重載C++運算符;另一種方案是,為特定類型提供具體化的模板定義,下面就介紹第二種方案:
(2)顯式實例化:
現在C++還允許顯式實例化(explicit instrantiation)。這意味着可以直接命令編譯器創建特定的實例,如swap<int>()。其語法是,聲明所需的種類——用<>符號指示類型,並在聲明前加上關鍵字template:
template void swap<int>(int, int); // explicit instrantiation
實現了這種特性的編譯器看到上述聲明后,將使用swap()模板生成一個使用int類型的實例。也就是說,該聲明的意思是"使用swap()模板生成int類型的函數定義。"
(3)顯式具體化:
與顯式實例化不同的是,顯式具體化使用下面兩個等價的聲明之一:
template <> void swap<int> (int &, int &); //explicit specialization template <> void swap (int &, int &); //explicit specialization
顯式實例化和顯式具體化區別在於:這些聲明的意思是“不要使用swap()模板來生成函數定義,而應使用專門為int類型顯式地定義的函數定義。”這些原型必須有自己的函數定義。顯式具體化聲明在關鍵字template后包含<>,而顯式實例化沒有。
警告:試圖在同一個文件中(或轉換單元)中使用同一種類型的顯式實例化和顯式具體化 將出錯。
隱式實例化、顯式實例化和顯式具體化統稱為具體化(specialization)。它們的相同之處在於,它們表示的都是使用具體類型的函數定義,而不是通用描述。
引入顯式實例化之后,必須使用新的語法——在聲明中使用前綴template和template<>,以區分顯式實例化和顯式具體化。通常,功能越多,語法規則也越多。
下面的代碼總結了這些概念:
…… struct job {}; template<typename T> void swap(T &, T &); // template prototype 模板 template<> void swap<job>(job &, job &); // explicit specialization for job 模板具體化 int main(void) { template void swap<char>(char &, char &); // explicit instrantiation for char 模板 顯式實例化 short a, b; …… swap(a, b); //implicit template instantiation for short(啟用一個模板) job n, m; …… swap(n, m); // use explicit specialization for job(模板具體化,不啟用模板) char g, h; …… swap(g, h); // use explicit template instantiation for char(啟用另一個模板) …… }
(4)部分具體化:
C++還允許部分具體化(partial speciazation),即部分限制模板的通用性。例如,部分具體化可以給類型參數之一指定具體的類型:
// general template template <typename T1, typename T2> class Pair { …… }; // specialization with T2 set to int template <typename T1> class Pair<T1, int> { …… };
關鍵字template后面的<>聲明的是沒有被具體化的類型參數。因此,上述第二個聲明將T2具體化為int,但T1保持不變。注意,如果指定所有的類型,則<>內將為空,這將導致顯式具體化:
// specialization with T1 and T2 set to int template <> class Pair<int, int> { …… };
如果有多個模板可供選擇,編譯器將使用具體化程度最高的模板。
下面是模板部分具體化在luaTinker中的實際應用例子:
// ------------------------------------------------------------------------------
/*** 以下代碼參考了 luaTinker 中 關於模板的使用 **/
#include <iostream> using namespace std; // "if_<bool, A, B>":類型選擇模板(使用模板來實現,根據參數不同匹配不同的模板) template<bool C, typename A, typename B> struct if_ {}; // 模板具體化(相對於一般模板,有更高的優先調用級;如果和具體化模板匹配,則調用具體化模板) // 模板的部分具體化(模板的參數依然是三個,只不過第一個參數被默認指定而已) template<typename A, typename B> struct if_<true, A, B> {typedef A type;}; // first place template<typename A, typename B> struct if_<false, A, B> {typedef B type;}; // second place int main() { if_<true, char, double>::type bb; // 直接匹配 first place 位置部分具體化模板函數 cout << " the sizeof(bb) is = " << sizeof(bb) << endl; if_<false, char, double>::type cc; // 直接匹配 second place 位置部分具體化模板函數 cout << " the sizeof(cc) is = " << sizeof(cc) << endl; /*下面使用兩個參數會報 參數不匹配 錯誤 * if_<char, int>::type dd; * cout << " the sizeof(dd) is = " << sizeof(dd) << endl; */ return 0; } // output ------------------------------------------------------------------------- the sizeof(bb) is = 1 the sizeof(cc) is = 8 請按任意鍵繼續. . .
二、編譯器選擇使用哪個函數版本:
對於函數重載、函數模板和函數模板重載,C++需要(且有)一個定義良好的策略,來決定為函數調用使用哪一個函數定義,尤其是有多個參數時。這個過程稱為重載解析(overloading resolution)。詳細解釋這個策略將需要將近一章的篇幅,因此這里我們只是大致了解一下這個過程是如何進行的。
(1)創建候選函數列表。其中包含與被調用函數的名稱相同的函數和模板函數。
(2)使用候選函數列表創建可行函數列表。這些都是參數數目正確的函數,為此有一個隱式的轉換序列,其中包括實參類型與相應的形參類型完全匹配的情況。例如,使用float參數的函數調用可以將該參數轉換為double,從而與double形參匹配,而模板可以為float生成一個實例。
(3)確定是否有最佳的可行函數。如果有,則使用它;否則,該函數調用出錯。
三、tuple 和 可變參數模板(C++11新標准添加功能):
【這部分轉自】http://www.cnblogs.com/hujian/archive/2012/02/23/2364190.html
C++11中引入的tuple是一個N元組。它相當於有N個成員的結構體,只不過這個結構體的成員都是匿名的。tuple中有兩個特殊的函數,一個是head(),用於獲取第一個成員的值,另一個是tail(),用於獲取剩下所有成員的值,tail()本身又是一個tuple。這樣,如果我們想取tuple中第二個成員的值,則可以先取tail()的值,再取tail()的head()的值。當然,這樣使用的話比較麻煩,所以C++ 11提供了get函數通過索引來獲取tuple中某個成員的值。另外,通過make_tuple可以很方便地構造一個tuple對象。有關tuple使用的例子可以參考下面的代碼。
tuple<int, char, string> tupInfo(10, 'A', "hello world"); int a = tupInfo.head(); int a2 = tupInfo.tail().head(); tuple<char, string> tupTail = tupInfo.tail(); int b = get<0>(tupInfo); char c = get<1>(tupInfo); string s = get<2>(tupInfo); auto tupInfo2 = make_tuple(5, 'B', string("C++ 11"), 4.6);
前面說過,tuple是一個N元組,而N的個數是沒有限制的,也就是說,tuple可以包含0個、1個、2個或更多的元素,每個元素的類型則通過模板參數指定。那么,tuple是如何做到這些的呢?答案是可變參數模板。學習C++的人應當對printf函數都非常熟悉,printf的一個特點就是它的參數個數是可變的。而在C++ 11中,則允許模板的參數個數也是可變的。下面是一個模板參數可變的函數模板,用於獲取傳入的參數的個數。
template<typename... Args> UINT GetParameterCount(Args... args) { return sizeof...(args); }
可以看到,可變參數模板使用typename再加...來表示模板參數包,使用Args再加...來表示函數參數包。上面代碼中的sizeof...專門用於獲取函數參數包中參數的個數,它的參數必須是一個函數參數包類型的對象。熟悉了可變參數模板的基本語法后,下面我們使用它來編寫一個Print函數,該函數的參數個數和類型都是可變的,它簡單地輸出傳入的各個參數的值,值之間用逗號進行分割,並在輸出最后一個參數的值后自動換行。
template<typename T> void Print(T value) { cout << value << endl; } template<typename Head, typename... Rail> void Print(Head head, Rail... rail) { cout << head << ","; Print(rail...); // 遞歸調用可變參數模板 } int main(int argc, char *argv[]) { Print(1); // 輸出:1 Print(1, "hello"); // 輸出:1,Hello Print(1, "hello", 'H'); // 輸出:1,Hello,H return 0; }
在上面的代碼中,我們先定義了一個只有一個模板參數的函數模板,它簡單地輸出傳入的參數的值。然后又定義了一個可變參數的函數模板,它輸出第一個參數的值,然后遞歸地調用自己。注意rail...這種寫法,它表示將函數參數包分割成一個一個的參數,並傳入Print中。這樣,函數參數包中的第一個參數傳遞給head,剩余的參數又重新構成一個函數參數包傳遞給rail。當遞歸調用到函數參數包中只有一個參數時,則會調用只有一個模板參數的Print函數。
下圖是對可變參數模板一個調用過程中參數傳遞變化的圖解:
四、模板類與友元
模板類聲明也可以有友元。模板的友元分3類:
非模板 友元。(類模板具體化:非模板友元 == n:1)
約束模板 友元,即友元的類型取決於類被實例化時的類型。(類模板具體化:非模板友元 == 1:1)
非約束模板 友元,即友元的所有具體化都是類的每一個具體化的友元。(類模板具體化:非模板友元 == 1:n)
(1)模板類的非模板友元函數(類模板具體化:非模板友元 == n:1)
在模板類中將一個常規函數聲明為友元:
template <class T> class HasFriend { public: friend void counts(); // }
上述聲明使counts()函數成為模板所有實例化的友元。例如,它將是類HasFriend<int>和hasFriend<string>的友元。
counts()函數不是通過對象調用的(它是友元,不是成員函數),也沒有對象參數,那么它如何訪問HasFriend對象呢?有很多種可能性。它可以訪問全局對象;可以使用全局指針訪問非全局對象;可以創建自己的對象;可以訪問獨立於對象的模板類的靜態數據成員。
假設 要為友元函數提供 模板類參數,可以如下所示來進行友元聲明嗎?
friend void report(HasFriend &);
答案是不可以。原因是不存在HasFriend這樣的對象,而只有特定的具體化,如HasFriend<short>。要提供模板類參數,必須指明具體化。例如,可以這樣做:
template <class T> class HasFriend { friend void report(HasFriend<T> &); …… };
(2) 模板類的約束模板友元函數(類模板具體化:非模板友元 == 1:1)
使友元函數本身成為模板,具體地說,為約束模板友元作准備,要使類的每一個具體化都獲得與友元匹配的具體化。包含以下3步。
1. 在類定義的前面聲明每個模板函數。
template<typename T> void counts(); template<typename T> void report(T &);
2. 在函數中再次將模板聲明為友元。這些語句根據類模板參數的類型聲明具體化(帶<>尖括號):
template<typename TT> class HasFriendT { …… friend void counts<TT>(); friend void report<>(HasFriendT<TT> &); };
聲明中的<>指出這是模板具體化。對於report(),<>可以為空,因為可以從函數參數推斷出如下模板類型參數:
HasFriendT<TT>
然而,也可以使用:
report<HasFriendT<TT> >(HasFriendT<TT> &)
但counts()函數沒有參數,因此必須使用模板參數語法(<TT>)來指明其具體化還需要注意的是,TT是HasFriendT類的參數類型。
3. 程序必須滿足的第三個要求是,為友元提供模板定義,也就是友元模板的實現函數體。
template<typename T> void counts() { cout << "template size :" << sizeof(HasFriendT<T>) << ";" ; cout << "template counts(): " << HasFriendT<T>::item << endl; }
(3)模板類的非約束模板友元函數(類模板具體化:非模板友元 == 1:n)
前一節中的約束模板友元函數是在類外面聲明的模板的具體化,int類具體化獲得int函數具體化,依此類推。
通過在類內部聲明模板,可以創建非約束模板友元函數,即每個函數具體化都是每個類具體化的友元。對於非約束友元,友元模板類型參數與模板類類型參數是不同的:
template<typename T> class ManyFriend { …… template <typename C, typename D> friend void show2(C &, D &); };
下面的程序是一個使用非約束友元的例子。其中,函數定義show2(hfi1, hfi2)與下面的具體化匹配:
void show2<ManyFriend<int> &, ManyFriend<int> &>(ManyFriend<int> & c, ManyFriend<int> & d);
因為它是所有ManyFriend具體化的友元,所以能夠訪問所有具體化的item的成員,但它只訪問了ManyFriend<int>對象。
同樣,show2(hfd, hfi2)與下面具體化匹配:
void show2<ManyFriend<double> &, ManyFriend<int> &>(ManyFriend<double> & c, ManyFriend<int> & d);
它也是所有ManyFriend具體化的友元,並訪問了ManyFriend<double>對象的item成員和ManyFriend<int>對象的item成員。
#include<iostream> using std::cout; using std::endl; template<typename T> class ManyFriend { private: T item; pulbic: ManyFriend(const T & i) : item(i) {} template<typename C, typename D> void show2(C & c, D & d); }; template <typename C, typename D> void show2(C & c, D & d) { cout << c.item << " , " << d.item << endl; } int main() { ManyFriend<int> hfi1(10); ManyFriend<int> hfi2(20); ManyFriend<double> hfdb(10.5); cout << "hfi1, hfi2 : "; show2(hfi1, hfi2); cout << "hfdb, hfi2 : "; show2(hfdb, hfi2); return 0; }
// output
hfi1, hfi2 : 10, 20
hfdb, hfi2 : 10.5, 20
其他內容如:遞歸使用模板,模板參數,類模板,成員模板,模板別名等,以后待續……
參考:《C++ Primer Plus(第六版)》、《Effective C++》……