1.1Function Template初窥
所谓的function templates是指由参数化手段表现一整个族群的function。
一个function templates可以表示一族(一整群)functions,其表现和一般的function并无二致,只是其中某些元素在编写时尚未确定,换而言之,那些[尚未确定的元素]被[参数化]了。
1.1.1定义Template
下面的function templates传回两个数值中的较大者:
inline T const & max( T const & a, T const & b )
{
return a < b ? b : a
}
这一份template定义式代表了一整族functions,他们的作用都是传回a和b两参数中的较大者。两个参数的型别都尚未确定,我们说它"template parameter T"。如你所见,template parameters必须以如此形式加以宣告:
template<以逗号分隔的参数列>
上述例子中,参数列就是typename T。请注意,例子中的[<]和[>]在这里被当作尖括号使用。关键字typename引入了一个所谓的type parameter(型别参数)------这是目前为止C++程式中最常使用的一种template parameter,另还存在其他种类的template parameter(如:nontype parameter,[非型别参数]),这个以后讨论。
此处的型别参数是T,你也可以使用其他任何标识符号(identifier)来表示型别参数,但习惯写成T(译注:代表Type)。Type parameter可标识任何型别,在function templates被呼叫时,经由传输具体型别而使T得以被具体指定。你可以使用任何型别,只要它支持T所要完成的操作。本列中型别T必须支援operator<以比较两值大小。
由于历史因素,你也可以使用关键字class代替关键字typename来定义一个type parameter。关键字typename是C++发展晚期才引进的,在此之前只能经由关键字class引入type parameter。关键字class目前依然可以用。因此template max()也可以被写成如下等价形式:
inline T const & max(T const & a, T const & b)
{
return a < b ? b : a;
}
就语义而言,前后两者毫无区别。即便使用关键字class,你还是可以把任意型别(包括non-class型别)当作实际的template arguments。但是这么写可能带来一些误导(让人误以为T必须是class型别),所以最好还是使用关键字typename。请注意,这和class的型别宣告并不是同一回事:宣告type parameters时我们不能把关键字typename换成关键字struct。
1.1.2使用Template
以下程式示范如何使用max() function template:
#include <string>
#include "max.hpp"
int main()
{
int i = 42;
std::cout<<"max(7,i): "<<::max(7,i)<<std::endl;
double f1 = 3.4;
double f2 = -6.7;
std::cout<<"max(f1,f2): "<<::max(f1,f2)<<std::endl;
std::string s1 = "mathematics";
std::string s2 = "math";
std::cout<<"max(s1,s2): "<<::max(s1,s2)<<std::endl;
}
程序呼叫了max()三次。第一次参数是两个int,第二次参数是两个double,最后一次参数是两个std::string。每一次max()均比较两值取大者。程序运行结果为:
max(f1,f2): 3.4
max(s1,s2): mathematics
注意程序对max()的三次呼叫都加了前缀符号"::",以遍确保被唤起的是我们在全域名空间(global namespace)中定义的max()。标准程序库内也有一个std::max() template,可能会在某些情况下被唤起,或在呼叫时引起模棱两可(ambiguity,歧义性,如果某个引数的型别定义于namespace std中,例如string,根据C++搜寻规则,::max()和std::max()都会被找到,那就会引起歧义性)。
一般而言,templates不会被编译为[能够处理任意型别]的单一实物(entity),而是被编译为多个个别实物,每一个处理某一特定型别。([一份实物,适用所有型别],理论上成立,实际不可行。毕竟所有语言规则都奠基于[将会产生出不同实物]的概念all language rules are based on the concept that different entities are generated)。因此,针对三个型别,max()被编译成三个实物。
int i = 42;
...max(7,i)...
使用的是[以int为template parameter T]的funciton template,语义上等同于呼叫以下函数:
{
return a < b ? b : a;
}
以具体型别替换template parameters的过程称为[具现化](instantiation,或称[实体化])。过程中会产生template的一份实体(instance)。不巧的是,instantiation和instance这两个术语在OO(面向对象)编程领域中有其他含义,通常用来表示一个class的具体物件(concrete object)。
注意,只要function template被使用,就会自动引发具现化过程。程序员没有必要个别申请具现过程。
类似情况,另两次对max()的呼叫被具现化为:
const std::string & max(std::string const &, std::string const &);
如果试图以某个型别来具现化function template,而该型别并未支援function template中用到的操作,就会导致编译错误。例如:
...
max(c1,c2); //编译期出错
实际上,template会被编译两次:
1.不具现化,只是对template程序代码进行语法检查以发现诸如[缺少分号;]等等的语法错误。
2.具现化,编译器检查template程序代码中的所有呼叫是否合法,诸如[未授支援函数调用]便会在这个阶段被检查出来。
这会导致一个严重问题:当function template被运用而引发具现化过程时,某些时候编译器需要用到template的原始定义。一般情况下,对普通的(non-template)functions而言,编译和连结两步骤是各自独立的,编译器只检查各个functions的宣告式是否和呼叫式相符,然后template的编译破坏了这个规则。眼下我们用最简单的解法:把template程序代码以inline形式写在表头档(header)中。