C++函数模板用法

C++函数模板详解

函数模板的代码格式:

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类型的模板函数
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值