这两天在学习《C++ primer》这本书时,发现有关函数与类模板的相关内容多且繁琐,而且容易混淆,因此决定写一篇博客,将它们的概念与之间的区别梳理一下。
一.函数模板
在《C++ primer》一书中,函数模板的具体化包括了三个部分:显式具体化,隐式实例化与显式实例化。
1.隐式实例化
隐式实例化是我们最常见的类型:根据定义的通用函数模板,编译器按照调用的参数生成特定的模板。如有以下的模板定义:
template <typename T>
void myswap(T &a,T &b)
{
}
在其后的代码块中,如果函数调用int参数,编译器就会根据模板生成myswap()函数的一个实例:
int a=1,b=2;
myswap(a,b);
2.显式实例化
最初,编译器只能通过隐式实例化生成模板实例,现在允许在编译阶段生成模板函数的实例,而非运行阶段。这种方式叫做显式实例化。显式实例化的书写方法与隐式实例化存在一定的不同,仍以myswap()函数为例,如下所示:
template void myswap<char>(char, char);
3.显式具体化
模板函数自身有一定的局限性。由于其采用对对象类型的泛化处理,导致其在内部实现的细节上无法保证对所有类型的通用性。如myswap()函数的设计目的是交换两个对象,如果对象是标准数据类型,则直接交换它们的值即可。但如果对象是企业两个员工的个人信息,那么实现起来就有很多的不同。为解决这一问题,c++中提供对模板的“特化”处理,即显式具体化。显式具体化的声明如下:
template <> void myswap<double>(double &, double &)
{
}
显式具体化与显式实例化书写上的差别在于后者需要在template后添加一个<>,另外参数类型中的引用符号不可以省略。在实际使用过程中,编译器调用的顺序是:显式具体化>隐式实例化。同时,需要注意,不可以同时在一个文件中声明同一类型的显式实例化与显式具体化,否则编译器会报错。
二.类模板
类模板具体化的分类与函数模板有相似也有不同。除了隐式实例化,显式实例化与显式具体化之外,在《c++ primer》一书中还提到一种部分具体化。
1.隐式实例化
类模板隐式实例化的含义与函数模板隐式实例化的含义接近,即根据定义好的类模板,由编译器按照指定的参数类型生成具体的类定义。如有下列的类定义:
template <typename T1, typename T2>
class myclass
{
T1 a;
T2 b;
public:
myclass(T1 t1,T2 t2){};
};
当出现如下的调用形式时:
int a=1,b=2;
myclass<int,int> t(a,b);
即代表所谓的隐式实例化。值得注意的是:myclass并不是一个类,只有当它与特定的类型相关联时,才成为一个类,就如上面的myclass<int ,int>。
2.显式实例化
类模板的显式实例化与函数模板的显式实例化含义基本一致,即使没有声明或者创建类对象,编译器也会自动生成类的声明。类模板的显式实例化声明格式如下所示:
template class myclass<int,int>;
3.显式具体化
类模板显式具体化的含义与函数模板显式具体化的含义相似,当类模板的参数为特定类型时,类行为会与类模板的定义有所不同,需要对其进行“特化”。这就是类模板的显式具体化。仍然以myclass()类为例,其显式具体化的定义如下所示:
template <> class myclass<char,char>
{
};
区别就在于后者在template后面加上了一个<>。当编译器遇上声明为:myclass<char,char> m;的变量时,会调用显式具体化中的实现。同样,在同一段程序中不可以同时出现对同一种类型的显式实例化与显式具体化的定义。
4.部分具体化
类模板的参数往往有多个,如果需要对部分参数具体化的类的行为进行定义,则将用到部分具体化。在侯捷老师的《STL 源码剖析》一书中又将其称为“偏特化”。其声明即是在显式具体化的基础上将部分模板参数变为抽象类型,如下:
template <typename T> class myclass<T,double>
{
};
如果有多个可供选择的模板,编译器将选择具体化程度最高。如下面四个例子,分别对应了上面的四种声明与定义:
myclass<long,long> m(1,0); //隐式实例化
myclass<int,int> r(1,0); //显式实例化
myclass<char,char> c; //显式具体化
myclass<int,double> d; //部分具体化