模板(Template)指C++程序设计语言中的函数模板与类模板,是一种参数化类型机制。模板是C++泛型编程中不可缺少的一部分。
C++ templates enable you to define a family of functions or classes that can operate on different types of information.
模板就是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数,从而实现了真正的代码可重用性。
模板的声明与定义:模板定义以关键字template开始,后接模板形参表(template parameter list),模板形参表是用尖括号括住的一个或者多个模板形参的列表,形参之间以逗号分隔。模板形参可以是表示类型的类型形参(type parameter),也可以是表示常量表达式的非类型形参(non-type parameter)。非类型形参跟在类型说明符之后声明。类型形参跟在关键字class或typename之后声明。模板形参可以给出默认值(default arguments for template parameters)。
模板的非类型形参(template non-type parameter)允许为以下形式:(1)、整型或枚举型;(2)、到对象的指针或函数指针;(3)、到对象的引用或函数引用;(4)、成员指针。
模板的非类型参数被声明为数组或函数的,将被转换为指针或函数指针。模板的非类型形参允许用const或volatile限定(而模板的类型形参是不允许用const或volatile限定的)。模板的非类型形参是不允许声明为浮点型、class类型、void型。
模板的模板参数:类模板的模板参数允许是另外一个类模板,这称为模板的模板参数(template template parameter),也译作“模板参数模板”。函数模板不允许有模板的模板参数。
模板参数的默认值:模板形参可以给出默认值(default arguments for template parameters)。如果一个模板参数给出了默认值,那么模板形参列表中在其后声明的模板参数都应该给出默认值。
一个模板的各次声明给出的模板参数的默认值可以累积其效果。模板参数的作用域为从其声明之处至该模板的定义结束之处。因此可以使用一个模板参数作为其后声明的其他模板参数的一部分或默认值。
模板的使用:使用模板时,可以在模板名字后面显式给出用尖括号括住的模板实参列表(template argument list)。对模板函数或类的模板成员函数,也可不显式给出模板实参,而是由编译器根据函数调用的上下文推导出模板实参,这称为模板参数推导。
如果模板参数使用其默认值,则在模板实参列表中可以忽略它。如果所有的模板参数都使用了默认值,模板实参列表为空,但仍然必须写出成对的尖括号。
对于作为类型的模板实参,不允许是局部类型(local type)、无链接性的类型(type with no linkage)、无名类型(unnamed type)或包括了这三种情形的复合类型。但C++11以允许本地类型作为模板实参。
模板的嵌套:成员模板,对于类中的模板成员函数、嵌套的成员类模板,可以在封闭类的内部或外部定义它们。当模板成员函数、嵌套类模板在其封闭类的外部定义时,必须以封闭类模板的模板参数(如果它们也是模板类)和成员模板的模板参数开头。C++标准规定:如果外围的类模板没有特例化,里面的成员模板就不能特例化。
依赖名字与typename关键字:一个模板中的依赖于一个模板参数(template parameter)的名字被称为依赖名字(dependent name)。当一个依赖名字嵌套在一个类的内部时,称为嵌套依赖名字(nested dependent name)。一个不依赖于任何模板参数的名字,称为非依赖名字(non-dependent name)。
编译器在处理模板定义时,可能并不确定依赖名字表示一个类型,还是嵌套类的成员,还是类的静态成员。C++标准规定:如果解析器在一个模板中遇到一个嵌套依赖名字,它假定那个名字不是一个类型,除非显式用typename关键字前置修饰该名字。
typename关键字有两个用途:(1)、常见的在模板定义中的模板形参列表,表示一个模板参数是类型参数。等同于使用class。(2)、使用模板类内定义的嵌套依赖类型名字时,显式指明这个名字是一个类型名。否则,这个名字会被理解为模板类的静态成员名。C++11起,这一用途也可以出现在模板以外,尽管此时typename关键字不是必要的。
在下述情形,对嵌套依赖类型名字不需要前置修饰typename关键字:(1)、派生类声明的基类列表中的基类标识符;(2)、成员初始化列表中的基类标识符;(3)、用class、struct、enum等关键字开始的类型标识符。因为它们的上下文已经指出这些标识符就是作为类型的名字。
template关键字有两个用途:(1)、常见的在模板定义的开始;(2)、模板类内部定义了模板成员函数或者嵌套的成员模板类。在模板中,当引用这样的模板成员函数或嵌套的成员模板类时,可以在::(作用域解析)运算符、.(以对象方式访问成员)运算符、->(以指针方式访问成员)运算符之后使用template关键字,随后才是模板成员函数名字或嵌套的成员模板类名字,这使得随后的左尖括号<被解释为模板参数列表的开始,而不是小于号运算符。C++11起,这一用途也可以出现在模板以外,尽管此时template关键字不是必要的。
模板实例化(template instantiation):是指在编译或链接时生成函数模板或类模板的具体实例源代码。ISO C++定义了两种模板实例化方法:隐式实例化(当使用实例化的模板时自动地在当前代码单元之前插入模板的实例化代码)、显式实例化(直接声明模板实例化)。在C++语言的不同实现中,模板编译模式(模板初始化的方法)大致可分为三种:
(1)、Borland模型(包含模板编译模式):编译器生成每个编译单元中遇到的所有的模板实例,并存放在相应的目标文件中;链接器合并相同的模板实例,生成可执行文件。为了在每次模板实例化时模板的定义都是可见的,模板的声明与定义放在同一个.h文件中。这种方法的优点是链接器只需要处理目标文件;这种方法的缺点是由于模板实例被重复编译,编译时间被加长了,而且不能使用系统的链接器,需重新设计链接器。
(2)、Cfront/查询模型(分离(Separation)模板编译模式):AT&T公司的C++编译器Cfront为解决模板实例化问题,增加了一个模板仓库,用以存放模板实例的代码并可被自动维护。当生成一个目标文件时,编译器把遇到的模板定义与当前可生成的模板实例存放到模板仓库中。链接时,链接器的包装程序(wrapper)首先调用编译器生成所有需要的且不在模板仓库中的模板实例。这种方法的优点是编译速度得到了优化,而且可以直接使用系统的链接器;这种方法的缺点是复杂度大大增加,更容易出错。使用这种模型的源程序通常把模板声明与非内联的模板成员分别放在.h文件与模板定义文件中,后者单独编译。
(3)、混合(迭代)模型:g++目前是基于Borland模型完成模板实例化。g++未来将实现混合模型的模板实例化,即编译器把编译单元中的模板定义与遇到的当前可实现的模板实例存放在相应的目标文件中;链接器的包装程序(wrapper)调用编译器生成所需的目前还没有实例化的模板实例;链接器合并所有相同的模板实例。使用这种模型的源程序通常把模板声明与非内联的模板成员分别放在.h文件与模板定义文件中,后者单独编译。
ISO C++标准规定,如果隐式实例化模板,则模板的成员函数一直到引用时才被实例化;如果显式实例化模板,则模板所有成员立即都被实例化,所以模板的声明与定义在此处都应该是可见的,而且在其它程序文本文件使用了这个模板实例时用编译器选项抑制模板隐式实例化,或者模板的定义部分是不可见的,或者使用template<> type FUN_NAME(type list)的语句声明模板的特化但不实例化。
关于模板实例化(template instantiation):
(1)、指在编译或链接时生成函数模板或类模板的具体实例源代码,即用使用模板时的实参类型替换模板类型参数(还有非类型参数和模板型参数);
(2)、隐式实例化(implicit instantiation):当使用实例化的模板时自动地在当前代码单元之前插入模板的实例化代码,模板的成员函数一直到引用时才被实例化;
(3)、显式实例化(explicit instantiation):直接声明模板实例化,模板所有成员立即都被实例化;
(4)、实例化也是一种特例化,被称为实例化的特例(instantiated(or generated) specialization)。
隐式实例化时,成员只有被引用到才会进行实例化,这被称为推迟实例化(lazy instantiation)。
模板实例化是生成采用特定模板参数组合的具体类或函数(实例)。例如,编译器生成一个采用Array<int>的类,另外生成一个采用Array<double>的类。通过用模板参数替换模板类定义中的模板参数,可以定义这些新的类。
关于模板的编译和链接:
(1)、包含模板编译模式:编译器生成每个编译单元中遇到的所有的模板实例,并存放在相应的目标文件中;链接器合并等价的模板实例,生成可执行文件,要求实例化时模板定义可见,不能使用系统链接器;
(2)、分离模板编译模式(使用export关键字):不重复生成模板实例,编译器设计要求高,可以使用系统链接器;
(3)、包含编译模式是主流,C++11已经弃用export关键字(对模板引入extern新用法),一般将模板的全部实现代码放在同一个头文件中并在用到模板的地方用#include包含头文件,以防止出现实例不一致。
关于template、typename、this关键字的使用:
(1)、依赖于模板参数(template parameter,形式参数,实参英文为argument)的名字被称为依赖名字(dependent name),C++标准规定,如果解析器在一个模板中遇到一个嵌套依赖名字,它假定那个名字不是一个类型,除非显式用typename关键字前置修饰该名字;
(2)、和上一条typename用法类似,template用于指明嵌套类型或函数为模板;
(3)、this用于指定查找基类中的成员(当基类是依赖模板参数的类模板实例时,由于实例化总是推迟,这时不依赖模板参数的名字不在基类中查找)。
C++11 关于模板的新特性:
(1)、”>>”根据上下文自动识别正确语义;
(2)、函数模板参数默认值;
(3)、变长模板参数(扩展sizeof…()获取参数个数);
(4)、模板别名(扩展using关键字);
(5)、外部模板实例(拓展extern关键字),弃用export template。
函数模板