模板進階
1. 非類型模板參數
模板參數分爲:類型形參 與 非類型形參。
類型形參:出現在模板參數列表中,跟在class或者typename之後的參數類型名稱。
非類型形參:就是用一個常量作爲類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。
//定義一個模板類型的靜態數組
template<class T, size_t N = 10>
class Array
{
public:
T& operator[](size_t index)
{
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;
};
注意: 浮點數、類對象以及字符串不允許作爲非類型模板參數;非類型模板參數必須在編譯期就能確認結果。
2.模板的特化
2.1 概念
通常情況下,使用模板可以實現一些與類型無關的代碼,但對於一些特殊類型的可能會得到一些錯誤的結果,如:
template<class T>
bool IsEuqal(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;
}
2.3 類模板特化
2.3.1 全特化
全特化是將模板參數列表中所有的參數都確定化
//類模板
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:
int _d1;
char _d2;
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
2.3.2 偏特化
任何針對模板參數進一步進行條件限制設計的特化版本。比如針對以下模板類:
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>
{
public:
Data() { cout << "Data<T1, int>" << endl; }
private:
T1 _d1;
int _d2;
};
· 參數更進一步限制:偏特化並不僅僅是指特化部分參數,而是針對模板參數更進一步的條件限制所設計出來的一個特化版本
//將兩個參數偏特化爲指針類型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:
Data() { cout << "Data<T1*, T2*>" << endl; }
private:
T1 _d1;
T2 _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;
};
void test()
{
Data<double, int> d1; //調用特化的int版本
Data<int, double> d2; //調用基礎的模板
Data<int*, int*> d3; //調用特化的指針版本
Data<int&, int&> d4(1,2); //調用特化的引用版本
}
3.類模板特化應用之類型萃取
問題引入:如何實現一個通用的拷貝函數?下面的代碼實現有問題嗎?
3.1 使用memcpy拷貝
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
memcpy(dst, src, sizeof(T)* size);
}
int main()
{
string str1[3] = { "11", "12", "13" };
string str2[3];
Copy(str2, str1, 3);
return 0;
}
上述代碼雖然對於任意類型的空間都可以進行拷貝,但是如果拷貝自定義類型對象就可能會出錯,因爲自定義類型對象有可能會涉及到深拷貝(比如string),而memcpy屬於淺拷貝。 如果對象中涉及到資源管理,就只能用賦值。
3.2 使用賦值方式拷貝
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
for (size_t i = 0; i < size; ++i)
{
dst[i] = sre[i];
}
}
此方法雖然可以,但是效率低下。C/C++程序最大的優勢就是效率高。那能否遇到內置類型就用memcpy來拷貝,遇到自定義類型就用循環賦值方式來做呢?
template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
if (IsPODType)
memcpy(dst, src, sizeof(T)* size);
else
{
for (size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
通過多增加一個參數,就可以將兩種拷貝的優勢體現結合起來。但缺陷是: **用戶需要根據所拷貝元素的類型去傳遞第三個參數,那出錯的可能性就增加。**那能否讓函數自動去識別所拷貝的類型是內置類型還是自定義類型呢?
這裏我們引入類型萃取
3.3 類型萃取
爲了將內置類型與自定義類型區分開,給出兩個類分別代表內置類型和自定義類型。
//代表內置類型
struct TrueType
{
static bool Get()
{
return true;
}
};
//代表自定義類型
struct FalseType
{
static bool Get()
{
return false;
}
};
給出以下類模板,將來用戶可以按照任意類型實例化該類模板
template<class T>
struct TypeTraits
{
typedef FalseType IsPODType;
};
對上述的類模板進行以下方式的特化:
template<class T>
struct TypeTraits<char>
{
typedef FalseType IsPODType;
};
template<class T>
struct TypeTraits<short>
{
typedef FalseType IsPODType;
};
template<class T>
struct TypeTraits<int>
{
typedef FalseType IsPODType;
};
...
//所有內置類型都特化一下
請看以下代碼片段:
/* T爲int: TypeTraits<int> 已經特化過,程序運行時就會使用已經特化過的TypeTraits<int>,該類中的
IsPODType剛好爲類TrueType,而TrueType中的Get函數返回爲true,內置類型使用memcpy拷貝
T爲string: TypeTraits<string>沒有特化過,程序運行時使用TypeTraits類模板,該類模板中的
IsPODType剛好爲類FalseType,而FalseType中Get返回false,自定義類型使用賦值方式拷貝
*/
template<class T>
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];
}
}