1、基模板
基模板指的是一个泛型模板定义,它为特化模板提供了基础,基模板可以有完整实现,也可以没有完整实现。
我们比较熟悉有完整实现的基模板,编译器编译时会替换掉模板参数,生成完整的函数或者类。
如果一个模板没有完整实现,那么我们使用它之前,必须要先实现它的特化版本。
// mytemplate.h
template <typename T>
class TestClass;
// main.cpp
template<>
class TestClass<int> {
public:
void print() {
std::cout << "int template" << std::endl;
}
};
int main() {
TestClass<int> tc;
tc.print();
// TestClass<double> tc2; // error:不允许使用不完整的类型
}
可以看到,虽然基模板template <typename T> class TestClass
没有完整实现但是我们依旧可以正常使用,这是因为cpp文件实现了int特化版本。如果我们创建double类型实例TestClass<double> tc2
则会报出不允许使用不完整的类型的error。
由于基模板为特化模板提供了基础,所以如果上例中没有基模板,那么编译器将会提示TestClass不是模板。
2、特化的实现位置
在简单模板一章的最后有一段说明 “模板类的方法实现必须要放在头文件当中”,这句话需要加一个限定词基模板。
上面的例子中可以看到模板定义在.h文件中,但是特化模板却实现在cpp文件中。
如果特化版本和基模板实现在一个文件中,说明实现的特化版本可以供所有编译单元使用;如果特化版本实现在某个cpp文件中,说明这个特化模板仅供当前编译单元使用。
3、偏特化
之前我们看到的偏特化都是以下形式,将模板的某个参数特化为具体类型:
template <typename T1, typename T2>
class TestClass2;
template <typename T1>
class TestClass2<T1, int> {
};
但是还有一种特化形式:
template <typename T1, typename T2>
class TestClass2;
template <typename T1>
class TestClass2<T1*, int&> {
};
T*
和int&
表明了当前特化版本适用的具体类型或类型模式,只有当第一个模板参数是T*
类型(指针类型),第二个模板参数是int&
(int引用)的情况下才会使用这个特化模板。
举一个具体的例子:
template <typename T1, typename T2>
class TestClass2 {
public:
void print() { std::cout << "template default" << std::endl; }
};
template <typename T1, typename T2>
class TestClass2<T1*, T2> {
public:
void print() { std::cout << "template specialization" << std::endl; }
};
int main() {
TestClass2<int, int> tc;
tc.print(); // template default
TestClass2<int*, int> tc2;
tc2.print(); // template specialization
}
模板特化可以要求模板参数为任何类型,常用的包括:
- 基本类型:如
int
、double
等; - 指针类型:
T*
及带修饰的const T*
等; - 引用类型:
T&
及带修饰的const T&
等; - 自定义类型:
- 数组类型:如
int[]
; - 函数指针类型:如
void (*)()
; - 函数类型:如
void()
,这一点暂时不去深入研究;
模板特化可以帮助我们提取类型的修饰,例如指针、引用、const、数组等等,这将在STL中有很大的作用。