目录
C++模板之隐式体化、显示体化、隐式调用、显示调用和模板特化区分
- 函数模板:中心词是模板,所以是指未体化的针对函数的模板
- 模板函数:中心词是函数,所以是指模板体化后的实际可使用的函数
- 类模板:中心词是模板
- 模板类:中心词是类
模板的体化指函数模板(类模板)生成模板函数(模板类)的过程。对于函数模板而言,模板体化之后,会生成一个真正的函数。而类模板经过体化之后,只是完成了类的定义,模板类的成员函数需要到调用时才会被初始化。模板的体化分为隐式体化和显示体化。
- 对于函数模板的使用而言,分为两种调用方式,一种是显示模板实参调用(显示调用),一种是隐式模板实参调用(隐式调用)。
- 对于类模板的使用而言,没有隐式模板实参和显式模板实参使用的说法,因为类模板的使用必须显示指明模板实参。各个概念请勿混淆。
1. 隐式体化
1.1 模板隐式体化的定义
这是相对于模板显示体化而言。在使用模板函数和模板类时,不存在指定类型的模板函数和模板类的实体时,由编译器根据指定类型参数隐式生成模板函数或者模板类的实体称之为模板的隐式体化。
1.2 函数模板隐式体化
函数模板隐式体化指的是在发生函数调用的时候,如果没有发现相匹配的函数存在,编译器就会寻找同名函数模板,如果可以成功进行参数类型推演,就对函数模板进行体化。
还有一种简介调用函数的情况,也可以完成函数模板的体化。所谓的简介调用是指将函数入口地址传给一个函数指针,通过函数指针完成函数调用。如果传递给函数指针不是一个真正的函数,那么编译器就会寻找同名的函数模板进行参数推演,进而完成函数模板的体化。参考如下示例。
#include <iostream>
using namespace std;
template <typename T> void func(T t){
cout<<t<<endl;
}
void invoke(void (*p)(int)){
int num=10;
p(num);
}
int main(){
invoke(func);
}
// 程序成功运行并输出10。
1.3 类模板隐式体化
类模板隐式体化指的是在使用模板类时才将模板体化,相对于类模板显示体化而言的。考察如下程序。
#include <iostream>
using namespace std;
template<typename T>class A{
T num;
public:
A(){
num=T(6.6);
}
void print(){
cout<<"A'num:"<<num<<endl;
}
};
int main(){
A<int> a; //显示模板实参的隐式实例化
a.print();
}
// 程序输出结果:A’num:6
2. 显示体化
2.1 模板显示体化的定义
显示体化也称为外部体化。在不发生函数调用的时候讲函数模板体化,或者在不使用类模板的时候将类模板体化称之为模板显示体化。
2.2 函数模板的显示体化
对于函数模板而言,不管是否发生函数调用,都可以通过显示体化声明将函数模板实体化,格式为:
template [函数返回类型] [函数模板名]<实际类型列表>(函数参数列表)
例如:
template void func<int>(const int&); // 显示体化声明前面必须加上关键字 template
2.3 类模板的显示体化
对于类模板而言,不管是否生成一个模板类的对象,都可以直接通过显示体化声明将类模板实体化,格式为:
template class theclass<int>; // 声明时,必须加上关键字template
3. 函数模板的调用方式
3.1 隐式模板实际参数调用
在发生函数模板的调用时,不显示给出模板参数而经过参数推演,称之为函数模板的隐式模板实参调用(隐式调用)。
template <typename T> void func(T t){
cout<<t<<endl;
}
func(5); //隐式模板实参调用
3.2 显示模板实参调用
在发生函数模板的调用时,显示给出模板参数而不需要经过参数推演,称之为函数模板的显示模板实参调用(显示调用)。
- 显示模板实参调用在参数推演不成功的情况下是有必要的。考察如下程序。
#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型,从而成功完成函数调用。
编程过程中,建议采用显示模板实参的方式调用函数模板,这样提高了代码的可读性,便于代码的理解和维护。
4. 模板特化
4.1 模板特化的定义
模板特化不同于模板的体化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化。
4.2 函数模板特化
函数模板特化是在一个统一的函数模板不能在所有类型实例下正常工作时,需要定义类型参数在体化为特定类型时函数模板的特定实现版本。简单理解就是:针对特定类型参数的定制版。查看如下例子。
#include <iostream>
using namespace std;
template<typename T> T Max(T t1,T t2){
return (t1>t2)?t1:t2;
}
typedef const char* CCP;
template<> CCP Max<CCP>(CCP s1,CCP s2){
return (strcmp(s1,s2)>0)?s1:s2;
}
int main(){
//调用实例:int Max<int>(int,int)
int i=Max(10,5);
//调用显示特化:const char* Max<const char*>(const char*,const char*)
const char* p=Max<const char*>("very","good");
cout<<"i:"<<i<<endl;
cout<<"p:"<<p<<endl;
}
// 程序正常编译运行结果:
i:10
p:very
在函数模板显示特化定义(Explicit Specialization Definition)中,显示关键字template
和一对尖括号<>
,然后是函数模板特化的定义。该定义指出了模板名、被用来特化模板的模板实参,以及函数参数表和函数体。在上面的程序中,如果不给出函数模板Max<T>
在T
为const char*
时的特化版本,那么在比较两个字符串的大小时,比较的是字符串的起始地址的大小,而不是字符串的内容在字典序中先后次序。
4.2.1 使用函数重载替代函数模板特化
除了定义函数模板特化版本外,还可以直接给出模板函数在特定类型下的重载形式(普通函数)。使用函数重载可以实现函数模板特化的功能,也可以避免函数模板的特定实例的失效。例如,把上面的模板特化可以改成如下重载函数。
typedef const char* CCP;
CCP Max(CCP s1,CCP s2){
return (strcmp(s1,s2)>0)?s1:s2;
}
程序运行结果和使用函数模板特化相同。但是,使用普通函数重载和使用模板特化还是有不同之处,主要表现在如下两个方面:
(1)如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。而如果使用模板的特化版本,除非发生函数调用,否则不会在目标文件中包含特化模板函数的二进制代码。这符合函数模板的“惰性体化”准则。
(2)如果使用普通重载函数,那么在分离编译模式下,应该在各个源文件中包含重载函数的申明,否则在某些源文件中就会使用模板函数,而不是重载函数。
4.3 类模板特化
类模板特化类似于函数模板的特化,即类模板参数在某种特定类型下的定制化实现。考察如下代码。
#include <iostream>
using namespace std;
template<typename T>class A{
T num;
public:
A(){
num=T(6.6);
}
void print(){
cout<<"A'num:"<<num<<endl;
}
};
template<>class A<char*>{
char* str;
public:
A(){
str="A' special definition ";
}
void print(){
cout<<str<<endl;
}
};
int main(){
A<int> a1; //显示模板实参的隐式实例化
a1.print();
A<char*> a2; //使用特化的类模板
A2.print();
}
// 程序输出结果如下:
A’num:6
A’ special definition
5. 总结
隐式体化指的是:在使用模板之前,编译器不生成模板的声明和定义实体。只有当使用模板时,编译器才根据模板定义生成相应类型的实体。如:int i=0, j=1;swap(i, j);
//编译器根据参数i,j的类型隐式地生成swap<int>(int &a, int &b)
的函数定义。Array<int> arVal;
//编译器根据类型参数隐式地生成Array<int>
类声明和类函数定义。
显式体化:当显式实体化模板时,在使用模板之前,编译器根据显式体化指定的类型生成模板实体。如前面显示实体化(explicit instantiation)模板函数和模板类。其格式为:template returntype functionname<typename>(argulist); template class classname<typename>;
显式实体化只需声明,不需要重新定义。编译器根据模板实现实体声明和实体定义。
特化:对于某些特殊类型,可能不适合模板实现,需要重新定义实现,此时可以使用特化(explicite specialization)。特化需重新定义。格式为:
template<> returnname function<typename>(argu_list){...};
template<> class classname<typename>{...};