一、什么是實例化和具體化?
為進一步了解模板,必須理解術語實例化和具體化。
(1)、實例化:在程序中的函數模板本身並不會生成函數定義,它只是一個用於生成函數定義的方案。編譯器使用模板為特定類型生成函數定義時,得到的是模板實例。這即是函數模板的實例化。而函數模板實例化又分為兩種類型:隱式實例化和顯式實例化
例如:
template < typename T > void Swap( T &a, T &b ) { T temp; temp = a; a = b; b = temp; } int main(void) { int a= 1, b = 2; Swap(a, b); Swap<int>(a, b); return 0; }
可以發現,在主函數中有兩種Swap函數調用。
第一個Swap(a, b)導致編譯器自動識別參數類型生成一個實例,該實例使用int類型,此為隱式實例化。
而第二個Swap<int>(a, b),直接命令編譯器創建特定的int類型的函數實例,用<>符號指示類型,此為顯式實例化。
(2)、具體化:即顯式具體化,與實例化不同的是,它也是一個模板定義,但它是對特定類型的模板定義。顯式具體化使用下面兩個等價的聲明之一:
template <> void Swap<int>(int &, int &);
template <> void Swap(int &, int &);
可以發現,顯式具體化聲明在關鍵字template后包含<>。上面聲明的意思是"不要使用Swap()模板來生成函數定義,而應使用專門為int類型顯式地定義的函數的定義"。這些原型必須有自己的函數定義。
在這里,有人可能要說了:”明明可以通過隱式實例化自動生成int類型的函數定義,為何還要弄出一個顯式具體化來弄出另外一個模板呢?這不是多此一舉嗎?”
我要解釋一下,顯式具體化的主要用途!而在介紹用途之前,我們先來了解一下普通函數模板的局限性。
二、模板的局限性
假設有如下模板函數:
template <typename T>
void fun(T a, T b)
{ ... }
通常,代碼假定可執行哪些操作。例如,下面的代碼假定定義了賦值。
a = b;
但是如果T為數組,這種假設將不成立!
同樣,下面的語句假設定義了<
if ( a > b )
但如果T為結構,則該假設便不成立!
另外,為數組名定義了運算符 > ,但由於數組名是常量地址,因此它比較的是數組的地址,而這並不是我們所期望的操作。下面的語句假定為類型T定義了乘法運算符,但如果T為數組、指針或結構,這種假設便不成立:
T c = a * b;
總之,編寫的模板函數很可能無法處理某些類型。通常在C++中有一種解決方案是:運算符重載,以便能將其用於特定的結構或類。就是說一個類重載了運算符+之后,使用運算符+的模板便可以處理重載了運算符+的結構。
但是,還有另外一種解決方案:為特定類型提供具體化的模板定義(這就是顯式具體化的主要用途)。
三、顯式具體化
假定定義了如下結構:
struct job { char name[40]; double salary; int floor; }
另外,假設希望能夠交換兩個這種結構的內容。原來的模板使用下面的代碼來完成交換:
temp = a;
a = b;
b = temp;
由於C++允許將一個結構賦給另一個結構,因此即使T是一個job結構,上述代碼也可適用。然而,如果只想交換salary和floor成員,而不交換name成員,則需要使用不同的處理代碼。但Swap() 的參數將保持不變(兩個job結構的引用),因此無法使用模板的重載來提供其他代碼(模板重載,模板的參數列表必須不同)。這時,就得用顯式具體化來實現這個需求。
上面已經介紹過顯式具體化的聲明方式,我們直接通過代碼實例來看一下:
#include <iostream> using namespace std; //job結構 struct job { char name[40]; double salary; int floor; }; //普通交換模板 template <typename T> void Swap(T &a, T &b) { T temp; temp = a; a = b; b = temp; } //job類型的顯式具體化模板 template <> void Swap<job>(job &j1, job &j2) { double t1; int t2; //交換salary t1 = j1.salary; j1.salary = j2.salary; j2.salary = t1; //交換floor t2 = j1.floor; j1.floor = j2.floor; j2.floor = t2; } int main(void) { int inta = 1, intb = 2; job zhangSan = {"張三", 80000, 6}; job liSi = {"李四", 60000, 4}; cout << "inta = " << inta << " inta = " << intb << endl; cout << zhangSan.name << " , " << zhangSan.salary << " , " << zhangSan.floor <<endl; cout << liSi.name << " , " << liSi.salary << " , " << liSi.floor <<endl; Swap(inta, intb); //編譯器將實例化普通模板的int類型函數 Swap(zhangSan, liSi); //編譯器將實例化顯式具體化模板job類型函數 cout << "\n交換后:\n" << endl; cout << "inta = " << inta << " inta = " << intb << endl; cout << zhangSan.name << " , " << zhangSan.salary << " , " << zhangSan.floor <<endl; cout << liSi.name << " , " << liSi.salary << " , " << liSi.floor <<endl; return 0; }
程序運行時匹配模板時,遵循的優先級是:
具體化模板優先於常規模板,而非模板函數優先於具體化和常規模板。
總結:隱式實例化和顯式實例化側重於函數調用,顯式具體化側重於函數定義。