模板参数
模板参数与作用域
模板参数遵循普通的作用域规则。一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。但是,与大多数其他上下文不同,在模板内不能重用模板参数名:
|
|
由于参数名不能重用,所以一个模板参数名在一个特定模板参数列表中只能出现一次:
|
|
模板声明
模版声明必须包含模板参数:
|
|
与函数参数相同,声明中的模板参数的名字不必与定义中相同。
建议: 一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。
使用类的类型成员
例如,假定T是一个类型参数的名字,当编译器遇到如下形式的语句时:
|
|
它需要知道我们是正在定义一个名为p的变量还是将一个名为size_type的static数据成员与名为p的变量相乘。
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点:
|
|
默认模板实参
就像我们能为函数参数提供默认实参一样,我们也可以提供默认模板实参(default template argument)。在新标准中,我们可以为函数和类模板提供默认实参。
例如,我们重写compare,默认使用标准库的less函数对象模板:
|
|
模板默认实参与类模板
无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。尖括号指出类必须从一个模板实例化而来。特别是,如果一个类模板为其所有模版参数都提供了默认实参,且我们希望使用这些默认实参,就必须在模板名之后跟一个空尖括号对:
|
|
习题示例:
编写函数,接受一个容器的引用,打印容器中的元素,两种方式,一种使用容器的size_type和size成员来控制打印元素的循环;另一种使用begin和end返回迭代器来控制循环。
|
|
成员模板
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)。成员模板不能是虚函数。
普通(非模板)类的成员模板
例如,我们定义一个类,我们的类将包含一个重载的函数调用运算符,它接受一个指针并对此指针执行delete。由于希望delete使用于任何类型,所以我们将调用运算符定义为一个模板:
|
|
类模板的成员模板
对于类模板,我们也可以为其定义成员模板。再此情况下,类和成员各自有自己的、独立的模板参数。
与类模板的普通函数成员不同,成员模板是函数模板。当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表:
|
|
控制实例化
当模板被使用时才会进行实例化这一特性意味着,相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板参数时,每个文件中就都会有该模板的一个示例。
在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显式实例化来避免这种开销。一个显式实例化有如下形式:
|
|
declaration是一个类或函数声明,其中所有模板参数已被替换为模板实参。例如,
|
|
当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。
警告: 对每个实例化声明,在程序中某个位置必须有其显式的实例化定义。
实例化定义会实例化所有成员
一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。当编译器遇到一个实例化定义时,它不了解程序使用哪些成员函数。因此,与处理类模板的普通实例化不同,编译器会实例化该类的所有成员。即使我们不使用某个成员,它也会被实例化。因此,我们用来显式实例化一个类模板的类型,必须能用于模板的所有成员。
note: 在一个类模板的实例化定义中,所有类型必须能用于模板的所有成员函数。