模板與泛型程式設計--模板編譯模型
引言:
當編譯器看到模板定義的時候,它不立即產生代碼。只有在用到模板時,如果調用了函數模板或定義了模板的對象的時候,編譯器才產生特定類型的模板執行個體。
一般而言,當調用函數時[不是模板],編譯器只需看到函數的聲明。類似的,定義類類型的對象時,類定義必須可用,但成員函數的定義不是必須存在的。因此,應該將類定義和函式宣告放在標頭檔中,而普通函數和類成員函數的定義放在源檔案中。
模板則不同:要進行執行個體化,編譯器必須能夠訪問定義模板的原始碼。當調用函數模板或類模板的成員函數的時候,編譯器需要函數定義,需要那些通常放在源檔案中的代碼。
標準C++為編譯模板代碼定義了兩種模型。在兩種模型中,構造程式的方式很大程度上是相同的:類定義和函式宣告放在標頭檔中,而函數定義和成員定義放在源檔案中。兩種模型的不同在於,編譯器怎樣使用來自源檔案的定義。如本書所述,所有編譯器都支援第一種模型,成為“包含”模型,只有一些編譯器支援第二種模型,“分別編譯”模型。
一、包含編譯模型
在包含編譯模型中,編譯器必須看到用到的所有模板的定義。一般而言,可以通過在聲明函數模板或類模板的標頭檔中添加一條#include指示使定義可用,該#include引入了包含相關定義的源檔案:
//in utilities.h#ifndef UTILITIES_H_INCLUDED#define UTILITIES_H_INCLUDED#include "utilities.cpp"template <class T>int compare(const T &,const T &);#endif // UTILITIES_H_INCLUDED
//in utilities.cpp#include "utilities.h"template <class T>int compare(const T &val1,const T &val2){ if (val1 < val2) return -1; if (val2 < val1) return 1; return 0;}
這一策略使我們能夠保持標頭檔和實現檔案的分離,但是需要保證編譯器在使用模板的代碼時能看到兩種檔案。
某些使用包含模型的編譯器,特別是較老的編譯器,可以產生多個執行個體。如果兩個或多個單獨編譯的源檔案使用同一模板,這些編譯器將為每個檔案中的模板產生一個執行個體。通常,這種方法意味著給定模板將執行個體化超過一次。在連結的時候,或者在預連結階段,編譯器會選擇一個執行個體化而丟棄其他的。在這種情況下,如果有許多執行個體化同一模板的檔案,編譯時間效能會顯著降低。對許多應用程式而言,這種編譯時間效能降低不大可能在現代電腦上成為問題,但是,在大系統內容中,編譯時間選擇問題可能變得非常重要。
這種編譯器通常支援某些機制,避免同一模板的多個執行個體化中隱含的編譯進開銷。編譯器最佳化編譯時間效能的方法各不相同。如果使用模板的程式的編譯時間難於承擔,請查閱編譯器的使用者指南,看看你的編譯器能提供什麼支援以避免多餘的執行個體化。
二、分別編譯模型
在分別編譯模型中,編譯器會為我們跟蹤相關的模板定義。但是,我們必須讓編譯器知道要記住給定的模板定義,可以使用export關鍵字來做這件事。
export關鍵字能夠指明給定的定義可能會需要在其他檔案中產生執行個體化。在一個程式中,一個模板只能定義為匯出一次。編譯器在需要產生這些執行個體化時計算出怎樣定位模板定義。export關鍵字不必在模板聲明中出現。
一般我們在函數模板的定義中指明函數模板為匯出的,這是通過在關鍵字template之前包含export關鍵字而實現的:
//in utilities.hexport template <typename Type>int compare(const Type &val1,const Type &val2)/***/
這個函數模板的聲明像通常一樣應放在標頭檔中,聲明不必指定export。
對類模板使用export更複雜一些。通常,類聲明必須放在標頭檔中,標頭檔中的類定義體不應該使用關鍵字export,如果在標頭檔中使用了export,則該標頭檔只能被程式中的一個源檔案使用。
相反,應該在類的實現檔案中使用export:
// in header file template <class Type> class Queue { ... };
// in implementation fileexport template <class Type> class Queue; #include "Queue.h" //...
匯出類的成員將自動聲明為匯出的。也可以將類模板的個別成員聲明為匯出的,在這種情況下,關鍵字export不在類模板本身指定,而是只在被匯出的特定成員定義上指定。匯出成員函數的定義不必在使用成員時可見。任意非匯出成員的定義必須像在包含模型中一樣對待:定義應放在定義類模板的標頭檔中。
//P544 習題16.27//in middle.h#ifndef MIDDLE_H_INCLUDED#define MIDDLE_H_INCLUDED#include <vector>#include <algorithm>using namespace std;template <typename Type>bool middle(const vector<Type> &,Type &);#include "middle.cpp"#endif // MIDDLE_H_INCLUDED
//in middle.cpp#include "middle.h"template <typename Type>bool middle(const vector<Type> &vec,Type &val){ vector<Type> tmp(vec); sort(tmp.begin(),tmp.end()); if (tmp.size() % 2 == 0) { return false; } typename vector<Type>::iterator index = tmp.begin() + tmp.size()/2; if (*index > *(index -1) && *(index) < *(index + 1)) { val = *index; return true; } return false;}
//in main.cpp#include <iostream>#include "middle.h"using namespace std;int main(){ int ia[] = {1,2,3,4,5,6,7}; int ai[] = {1,2,3,4,5,6}; vector<int> ivec1(ia,ia + 7),ivec2(ai,ai + 6); int val; if (middle(ivec1,val)) { cout << "Middle: " << val << endl; } else { cout << "No Middle!" << endl; } if (middle(ivec2,val)) { cout << "Middle: " << val << endl; } else { cout << "No Middle!" << endl; }}/**注意:g++編譯器支援包含模型*但是不能將模板的實現檔案包含到project中,*否則會引起編譯錯誤!*/