函数模板的代码格式:
template <typename T>
void swap(T &a, T &b)
{
...
}
template和typename关键句是必须的,但是typename可以用class替换(在标准C++98之前,一直使用关键字class)。
编译器会根据不同参数的模板函数来定义不同的函数,如swap函数接受两个int参数和swap函数接受两个double参数,编译器会分别定义两个独立函数,函数原型分别是:
void swap(int &a, int &b);
void swap(double &a, double &b);
所以C++的函数模板只是一种高度抽象的泛型编程技术,减少了手工代码的编写数量,但是无法缩短代码体积。
重载的模板
有时候并非所有的类型都使用相同的算法。因此C++ 模板函数也是可以像常规函数那样可以重载的(所谓重载就是函数名一样,函数参数类型或者参数数量不同的函数)。
如下面两个函数就是重载了swap模板函数:
template <typename T>
void swap(T &a, T &a) //original template
template <typename T>
void swap(T *a, T *b, int n); //new template
注意模板函数的参数并非一定要用泛型(T),也可以是具体的类型(如上面的int)。
普通函数和模板函数都可以使用相同的函数名。
模板的局限性
如果有以下模板函数,如果泛型T使用了某些数据类型(如数组,结构体),那么代码将无法正常运行:
template <typename T>
void f(T a , T b)
{
a = b; // 如果T为数组,无法运行
...
if(a >b) //如果T为结构体,无法运行
{
...
}
}
因此模板函数是由可能无法处理某些类型的。C++提供的解决方法是可以使用运算符重载技术(后续文章会介绍),或者是为特定类型提供具体化的模板定义,下面将介绍这种解决方法。
显式具体化
看下面这个例子,假如有个结构体:
struct job
{
char name[40];
double salary;
int floor;
};
如果我们想交换两个job变量中的salary和floor字段,但是不交换name字段,那么采用上文提到的swap交换函数是无法实现的。对于这种特殊需求,我们可以提供一个具体化函数定义——称为显式具体化(explicit specialization)。当编译器找到与函数调用具体定义时,将使用该定义而不再寻找模板。
显示具体化函数的原型如下:
//explicit specialization for the job type
template <> void swap<job>(job &, job &);
其中是可以省略的,template 被简化为template <>。
显式具体化函数的调用匹配优先级是高于模板函数的,也就是说如果参数类型与显式具体化函数完全一致,那么编译器调用的接口优先是显示具体化函数接口。下面代码可以说明:
template <typename T>
void Swap(T &, T &); //template
//explicit specialization for the job type
template <> void swap<job>(T & , T &);
int main()
{
double u,v;
job a,b;
swap(u,v); //use template
swap(a,b); //use explicit specialization
}
实例化和具体化
为了进一步理解模板,我们需要掌握两个概念:实例化和具体化。先说说实施化(instantiation)。
当你定义了一个函数模板时并非真正定义了一个函数实例,而只是定义了一种函数方案。当编译器发现你调用模板时,因为传入了具体的参数(比如int类型),编译器这时才会真正为模板创建一个int类型的函数定义,这种实例化方式称为隐式实例化(implicit instantation),所谓隐式就是说是由编译器自动搜寻匹配的模板、然后创建函数定义的方式。
与隐式实例化对应的是,C++还提供了显式实例化方式,这意味着可以通过声明直接命令编译器创建函数实例:
template void swap<int>(int ,int); //explicit instantiation
编译器看到这种声明后将使用swap模板生成一个使用int类型的实例,也就是说该声明的意思是“使用swap()模板生成int类型的函数定义”。
除了声明显示实例化外,还可以在程序中使用函数来创建显式实例化。看下面代码:
include <iostream>
template <typename T>
T add(T a , T b)
{
return a+b;
}
int main()
{
int m= 6;
double x = 10.2;
std::cout << add<double>(x,m) ;
}
这代码中模板与实际add函数实际调用的参数不一致,模板要求两个参数都是相同类型,实际却是一个double,一个int。但是通过显式实例化强制定义一个double类型的实例化函数,在调用时将参数m强制转换为double类型,就是调用普通函数的强制转换一样。
如果我们使用模板函数swap呢,如调用swap(x,m),是否能正常调用呢?答案是不能的。因为隐式实例化是由编译器自动搜寻匹配合适的模板函数,但是模板函数定义里是没有两个参数类型不同的类型的。
现在再来说说具体化。具体化就是显示具体化,在前面章节已提到。显示具体哈使用下面两个等价声明:
template <> void swap<int >(int & ,int &);
template <> void swap (int & ,int &);
具体化与实例化的区别在于,具体化的意思是“不要使用swap()模板来生成函数定义,而应使用专门为int类型显示定义的函数定义”。具体化的这些函数原型必须要有自己的函数定义,否则编译器会报错。具体化声明在关键字template后面包含<>,而显示实例化声明则没有。
总结下,隐式实例化、显示实例化和显示具体化统称为具体化(specialization)。他们共同之处都是使用具体类型定义函数,而不是泛型描述。
下面的示例代码总结了具体化的概念:
template <typename T>
void swap(T &, T &); //template prototype
template <> void swap<job> (job &, job &); //explicit specialization for job
int main()
{
template void swap<char>(char &, char &);//explicit instantiation for char
short a, b;
swap(a,b); //impicit template instantiation for shar
...
job n,m;
swap(n,m); //use explicit specialization for job
...
char g,h;
swap(g,h) //use explicit template instantiation for char
}
上面代码对char类型进行了显示实例化,因此swap(g,h)调用的是template void swap(char &, char &)接口。swap(a,b)没有显示实例化或具体化,因此匹配了模板函数,编译器生成short类型的函数定义。swap(n,m)则是调用了显示具体化的函数接口。
重载解析
编译器需要对同名函数进行解析,识别出函数重载、函数模板和函数模板重载,并根据策略选择最匹配的函数进行调用。这个过程称为重载解析(overloading resolution)。
编译器必须确定哪个函数是最佳匹配的。通常从最佳到最差的匹配顺序如下:
- 完全匹配。但是普通函数优于模板
- 提升转换(例如char和short自动转换为int,float自动转换为double)
- 标准转换(例如int转换为char, long 转换为double)
- 用户自定义的转换,如类声明中定义的转换
等价的匹配
左边实参与右边形参虽然符号有差别,但是他们两者是等价的。
手动选择函数
当出现普通函数和目标函数均为最佳匹配函数时,用户可以手动选择调用普通函数还是模板函数。请看以下示例:
#include <iostream>
using namespace std;
template <typename T>
T lesser(T a, T b)
{
return a<b?a:b;
}
int lesser (int a, int b) //普通函数
{
a = a<0?-a:a;
b = b<0?-b:b;
return a<b?a:b;
}
int main()
{
int m =20;
int n = -30;
double x = 15.5;
double y = 25.9;
cout<<lesser(n,m)<<endl; //普通函数优先级高,因此调用普通函数
cout<<lesser(x,y)<<endl;//参数double时,模板函数是最匹配的
cout<<lesser<>(m,n)<<endl; //手动调用模板函数
cout<<lesser<int>(x,y)<<endl;//显式实例化int类型的模板函数
}
C++函数模板详解
788

被折叠的 条评论
为什么被折叠?



