- 非類型模板參數
- 類模板的特化
- 類型萃取
- 模板的分離編譯
- 非類型模板參數
模板參數分類
- 類型形參:出現在模板參數列表中,跟在class或者typename之後的參數類型名稱。
- 非類型形參:就是用常量作爲類(函數)的一個參數,在類(函數)模板中可將該參數當成常量來使用
namespace fw{
template<class T, size_t N = 10>
class array{
public:
T& operator[](size_t index) const {
return _array[index];
}
const T& operator[](size_t index)const{
return _array[index];
}
size_t size() const{
return _size;
}
bool empty() const {
return 0 == _size;
}
private:
T _array[N];
size_t _size;
}
}
就是在模板中我們可以使用非類型模板參數,就像我們上面程序中的N就是模板類的一個參數,但是是一個size_t 顯式定義出來的。
注意:
- 浮點數,類對象以及字符串是不允許作爲非類型模板參數。
- 非類型的模板參數必須在編譯器就能確認結果。也就是我們需要給予初始值。
- 模板的特化
2.1概念
通常情況下,使用模板可以實現一些與類型無關的代碼,但是對於一些特殊類 型的可能會得到一些錯誤的結果。
template<class T>
bool IsEqual(T& left,T& right){
return left == right;
}
void test(){
char* p1 = "hello";
char* p2 = "world";
if(IsEqual(p1,p2)){
cout<<p1<<endl;
}else{
cout<<p2<<endl;
}
}
此時我們傳遞的是一個指針類型,如果我們執行函數的話比較的是兩個地址是否相同,我們的原本意願是比較內容是不是相同,此時我們需要對模板進行特化。即在原模板類的基礎上,針對特殊鎖進行特殊化的實現的方式。模板特化分爲函數模板特化與類模板特化。
2.2 函數模板的特化
步驟:
- 必須要先有一個基礎的函數模板
- 關鍵字template後接一對空的尖括號
- 函數名跟一對尖括號,尖括號中制定需要特化的類型
- 函數形參表:必須要和模板函數的基礎參數類型完全相同,如果不同編譯器可能會報一些奇怪的錯誤。
template <>
bool IsEqual<char*>(char*& left,char*& right){
if(strcmp(left,right)>0){
return true;
}
return false;
}
注意:一般情況下如果函數模板遇到不能處理或者處理有誤的類型,爲了實現簡單通常都是將該函數直接給出。
bool IsEqual(char*& left,char*& right){
if(strcmp(left,right)>0){
return true;
}
return false;
}
2.3 類模板特化
全特化即是將模板類表中所有的參數都確定化。
template <class T1,class T2>
class Data {
public: Data() {
cout<<"Data<T1, T2>" <<endl;
}
private: T1 _d1;
T2 _d2;
};
template<>
class Data<int, char> {
public: Data() {
cout<<"Data<int, char>" <<endl;
}
private:
T1 _d1;
T2 _d2;
};
void TestVector() {
Data<int, int> d1;
Data<int, char> d2;
}
偏特化
任何針對模板參數進一步進行條件限制設計的特化版本,
template<class T1, class T2>
class Data {
public:
Data() {
cout<<"Data<T1, T2>" <<endl;
}
private:
T1 _d1;
T2 _d2;
};
偏特化的變現形式:
- 部分特化
將模板參數類表中的一部分參數特化
就是在我們設計的時候給定一部分的參數
template<class T1>
class Data<T1, int>
- 參數進一步的限制
偏特化並不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本。
兩個參數特化爲指針類型:
template<class T, class T1>
class A<T*,T1*>{
public:
A(){
cout << "A<T*,T1*>" << endl;
}
private:
T = _d1;
T1 = _d2;
};
兩個參數特化爲引用類型
template <typename T1, typename T2>
class Data <T1&, T2&> {
public:
Data(const T1& d1, const T2& d2) :
_d1(d1), _d2(d2){
cout << "Data<T1&, T2&>" << endl
;
}
private:
const T1 & _d1;
const T2 & _d2;
};
- 類模板特化的應用值類型萃取
當我們使用函數模板實現一個memcpy函數的時候
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)*size);
}
雖然這是對memcpy函數,但是並不是對所有的類型都能成功的進行拷貝,當我們拷貝自定義類型對象就可能會出錯,因爲自定義類型對象可能涉及的深拷貝( 比如string),而memcpy屬於淺拷貝,如果對象中涉及到資源管理,就只能使用賦值。
c++中只要區分去對象,最內置類型和自定義類型進行分開拷貝,對於自定義類型使用賦值操作,對於內置類型就是用memcpy進行拷貝就可以實現。
類型萃取:就是爲了將內置類型與定義類型區分開。
可以使用我們上面講解的特化來實現。
定義兩個類來表示自定義類型和內置類型。
// 代表內置類型
struct TrueType {
static bool Get(){
return true;
}
};
// 代表自定義類型
struct FalseType {
static bool Get(){
return false;
}
};
給出以下類模板
template<class T>
struct TypeTraits{
typedef FalseType IsPODType;
}
將上述的類模板進行特化
比如我們的char和int類型。
template<>
struct TypeTraits<char> {
typedef TrueType IsPODType;
};
template<>
struct TypeTraits<int> {
typedef TrueType IsPODType;
};
將所有內置類型都進行特化。此時我們我們調用
void Copy(T* dst, const T* src, size_t size) {
if (TypeTraits<T>::IsPODType::Get())
memcpy(dst, src, sizeof(T)*size);
else {
for (size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
就可以根據傳遞的值T判斷類型。
- 模板分離編譯
分離編譯:一個程序(項目)由若干個源文件共同實現,而每個源文件單獨編譯生成目標文件,最後將所有目標文件鏈 接起來形成單一的可執行文件的過程稱爲分離編譯模式。
在模板中不支持分離編譯。必須聲明和定義在一起。原因:
編譯:需要對程序按照語言特性進行詞法,語法,語義分析,錯誤檢查無誤後生成彙編代碼,注意頭文件不參與編譯,編譯器對工程中的多個源文件是分離開單獨編譯的。
鏈接:將多個obj文件合成一個文件,並處理沒有解決的地址問題。
假如使用了分離編譯,在我們函數或者類實現的時候沒有對模板進行實例化,如果是函數不會生成具體的函數,在鏈接的時候因爲沒有生成函數的具體代碼,因此在鏈接的時候鏈接不到,此時就會報錯。如果我們在聲明的時候就定義出函數就直接生成函數。不會存在鏈接的問題。