模板特化的必要性
使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如:
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;
}
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
函数模板特化
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template <>
bool IsEqual<char*>(char*& left, char*& right) {
if(strcmp(left, right) > 0)
return true;
return false;
}
全特化
全特化即是将模板参数类表中所有的参数都确定化。
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;
}
偏特化
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
template <class T1, class T2>
class Data {
public:
Data() {cout<<"Data<T1, T2>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
偏特化有以下两种表现方式:
- 部分特化
将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
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 test2 () {
Data<double , int> d1; // 调用特化的int版本
Data<int , double> d2; // 调用基础的模板
Data<int *, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
应用之类型萃取
如何实现一个通用的拷贝函数?
当使用memcopy时,但如果拷贝自定义类型对象就可能会出错,因为自定义类型对象有可能会涉及到深拷贝(比如string),而memcpy属于浅拷贝。如果对象中涉及到资源管理,就
只能用赋值。
用循环赋值的方式会调用自定义对象的赋值运算符重载函数,用循环赋值的方式虽然可以,但是内置类型使用循环赋值的效率比较低。
- 使用函数区分内置于自定义类型
因为内置类型的个数是确定的,可以将所有内置类型集合在一起,如果能够将所拷贝对象的类型确定下来,在内置类型集合中查找其是否存在即可确定所拷贝类型是否为内置类型
bool IsPODType(const char* strType) {
const char* arrType[] = {"char", "short", "int", "long", "long long", "float","double", "long double"};
for(size_t i = 0; i < sizeof(array)/sizeof(array[0]); ++i) {
if(0 == strcmp(strType, arrType[i]))
return true;
}
return false;
}
template <class T>
void Copy(T* dst, const T* src, size_t size) {
if(IsPODType(typeid(T).name()))
memcpy(dst, src, sizeof(T)*size);
else {
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
通过typeid来确认所拷贝对象的实际类型,然后再在内置类型集合中枚举其是否出现过,既可确认所拷贝元素的类型为内置类型或者自定义类型。但缺陷是:枚举需要将所有类型遍历一遍,每次比较都是字符串的比较,效率比较低。
类型萃取
为了将内置类型与自定义类型区分开,给出以下两个类分别代表内置类型与自定义类型。
// 代表内置类型
struct TrueType {
static bool Get(){return true;}
};
// 代表自定义类型
struct FalseType {
static bool Get(){return false;}
};
template <class T>
struct TypeTraits {
typedef FalseType IsPODType;
};
template<>
struct TypeTraits<char> {
typedef TrueType IsPODType;
};
template<>
struct TypeTraits<short> {
typedef TrueType IsPODType;
};
template<>
struct TypeTraits<int> {
typedef TrueType IsPODType;
};
template<>
struct TypeTraits<long> {
typedef TrueType IsPODType;
};
// ... 所有内置类型都特化一下
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];
}
}
int main() {
int a1[] = {1,2,3,4,5,6,7,8,9,0};
int a2[10];
Copy(a2, a1, 10);
string s1[] = {"1111", "2222", "3333", "4444"};
string s2[4];
Copy(s2, s1, 4);
return 0;
}
模板分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
解决方法:
- 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。
- 模板定义的位置显式实例化。这种方法不实用……