为进一步了解模板,必须理解术语实例化和具体化。记住,在代码中包含函数模板本身并不会生成函数定义,它只是一个用于生成函数定义的方案。
编译器使用模板为特定类型生成函数定义时,这些编译器生成的版本通常被成为模板实例(instantiation)。例如,函数调用Swap(i, j)导致编译器生成一个int型参数的Swap( )函数定义。
这种实例化方式被称为隐式实例化(implicit instantiation),因为编译器之所以知道需要进行定义,是由于程序调用Swap( )函数时提供了int参数。
其实这一步应该是先生成函数定义,然后再完成了初始化。
当模板参数和调用参数没有发生关联,或者不能由调用参数来决定模板参数的时候,你在调用时就必须显式指定模板实参。
最初,编译器只能通过隐式实例化,来使用模板生成函数定义,但现在C++还允许显式实例化(explicit instantiation)。这意味着可以直接命令编译器创建特定的实例,如Swap( )。其语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:
实现了这种特性的编译器看到上述声明后,将使用Swap( )模板生成一个使用int类型的实例。也就是说该声明的意思是:使用Swap( )模板生成int类型的函数定义。
查看如下例子:
#include <iostream> using namespace std; template <typename T> T Max(const T& t1,const T& t2){ return (t1>t2)?t1:t2; } int main(){ int i=5; //cout<<Max(i,\'a\')<<endl; //无法通过编译 cout<<Max<int>(i,\'a\')<<endl; //显示调用,通过编译 }直接采用函数调用Max(i,’a’)会产生编译错误,因为i和’a’具有不同的数据类型,无法从这两个参数中进行类型推演。而采用Max< int>(i,’a’)调用后,函数模板的实例化不需要经过参数推演,而函数的第二个实参也可以由char转换为int型,从而成功完成函数调用。
编程过程中,建议采用显示模板实参的方式调用函数模板,这样提高了代码的可读性,便于代码的理解和维护。
具体化,specialization,即特殊化!
模版应该允许所有类型的输入。编程的健壮性就是如此。
但是有时类型的行为表现会不同,就无法用通用模版来表达。
如a+b,对于int 型是数值的加法运算,对于string是字符串的拼接,这样就无法用通用模版来表示,因此需要提供一个具体化函数定义——称为显式具体化(explicit specialization),其中包含所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
显式具体化:
对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载版本。
显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
具体化优先于常规模板,而非模板函数优先于具体化和常规模板。
相对于通用模版的通用型,显式具体化的作用是为了特殊化!即不使用通用实现方法,而是特殊实现方法。
与显式实例化不同的是,显式具体化使用下面两个等价的声明之一:
template <> 已经证明了这是个模版,而不是函数定义。
区别在于,这些声明的意思是“不要使用Swap( )模板来生成函数定义,而应使用专门为int类型显式地定义的函数定义”。这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化没有.
{%r%}
试图在同一个文件(或转换单元)中使用同一种类型的显式实例和显式具体化将出错。
{%endr%}
隐式实例化、显式实例化和显式具体化统称为具体化(specialization)。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。
隐式实例化指的是:在使用模板之前,编译器不生成模板的声明和定义实例。只有当使用模板时,编译器才根据模板定义生成相应类型的实例。如:int i=0, j=1;swap(i, j); //编译器根据参数i,j的类型隐式地生成swap(int &a, int &b)的函数定义。Array arVal;//编译器根据类型参数隐式地生成Array类声明和类函数定义。