前言
面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型了。
本书第Ⅱ部分中介绍的容器、迭代器和算法都是泛型编程的例子。当我们编写一个泛型程序时,是独立于任何特定类型来编写代码的。当使用一个泛型程序时,我们提供类型或值,程序实例可在其上运行。
例如,标准库为每个容器提供了单一的、泛型的定义,如vector。我们可以使用这个泛型定义来定义很多类型的vector,它们的差异就在于包含的元素类型不同。
模版是泛型编程的基础。我们不必了解模版是如何定义的就能使用它们,实际上我们已经这样用了。
模版是C++中泛型编程的基础。一个模版就是一个创建类或函数的蓝图或者说公式。当使用一个vector这样的泛型类型,或者find这样的泛型函数时,我们提供足够的信息,将蓝图转换为特定的类或函数。这种转换发生在编译时。
定义模版
函数模板
我们可以定义一个通用的函数模板(function template),而不是为每个类型都定义一个新函数。一个函数模板就是一个公式,可用来生成针对特定类型的函数版本。compare的模板版本可能像下面这样:
|
|
模版定义以关键字template开始,后跟一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数(template parameter)的列表,用小于号(<)和大于号(>)包围起来。
note: 在模板定义中,模板参数列表不能为空。
模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式地或显式地)指定模板实参(template argument),将其绑定到模板参数上。
T表示一个类型。而T表示的实际类型则在编译时根据compare的使用情况来确定。
实例化函数模板
当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参。即,当我们调用compare时,编译器使用参数的类型来确定绑定到模板参数T的类型。
|
|
实参类型是int。编译器会推断出模板参数为int,并将它绑定到模板参数T。
编译器用推断出的模板参数来为我们实例化(instantiate)一个特定版本的函数。
如上调用将实例化:
|
|
模板类型参数
一般来说,我们可以将模板类型参数看作类型说明符,就像内置类型或类类型说明符一样使用。特别是,类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换:
|
|
类型参数前必须使用关键字typename 或 class
|
|
typename是在模板已经广泛使用之后才引入C++语言的,推荐使用。
非类型模板参数
除了定义类型参数,还可以在模板中定义非类型参数(nontype parameter)。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定非类型参数。
当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
例如,我们可以编写一个compare版本处理字符串字面常量。这种字面常量是const char 的数组。由于我们不能拷贝一个数组,所以我们将自己的参数定义为数组的引用。由于我们希望能比较不同长度的字符串字面常量,因此为模板定义了两个非类型的参数。第一个模板参数表示第一个数组的长度,第二个模板参数表示第二个数组的长度:
|
|
当我们调用这个版本的compare时:
|
|
编译器会实例化出如下版本:
|
|
在模板定义内,模板非类型参数时一个常量值。在需要常量表达式的地方,可以使用非类型参数。
note: 非类型模板参数的模板实参必须是常量表达式。
inline 和 constexpr的函数模板
函数模板可以声明为inline或constexpr的,如同非模板函数一样。inline或constexpr说明符放在模板参数之后,返回类型之前。
|
|
编写类型无关的代码
我们最初的compare函数虽然简单,但它说明了编写泛型代码的两个重要原则:
- 模板中的函数参数是const的引用。
- 函数体中的条件判断仅使用<比较运算。
大多数类型都允许拷贝,但是,不允许拷贝的类类型也是存在的。通过设置为引用,保证这写类型可以处理。而且,当处理大对象时,这种设计策略还能使函数运行得更快。
注意: 模板程序应该尽量减少对实参类型的要求。
模板编译
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
note: 函数模板和雷模板成员函数的定义通常放在头文件中。
警告:保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,时调用者的责任。示例:定义自己版本的begin和end,同时编写一个constexpr模板,返回给定数组的大小
|
|
类模板
类模板(class template)是用来生成类的蓝图的。与函数模板的不同之处是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在模板名后的尖括号中提供额外信息—-用来代替模板参数的模板实参列表。
定义类模板
类似函数模板,类模板以关键字template开始,后跟模板参数列表。在类模板(及其成员)的定义中,我们将模板参数当作替身,代替使用模板时用户需要提供的类型或值。
实例化类模板
一个类模板的每个实例都形成一个独立的类。类型Blob
类模板的成员函数
类模板的成员函数本身是一个普通函数。但是,类模板的每个实例都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数。因而,定义在类模板之外的成员函数就必须以关键字template开始,后接类模板参数列表。
对应的Blob的成员应该是这样的:
|
|
类模板成员函数的实例化
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
在类代码内简化模板类名的使用
当我们使用一个类模板类型时必须提供模板参数,但这一规则有一个例外。在类模板自己的作用域中,我们可以直接使用模板名而不提供实参
在类模板外使用类模板名
当我们在类模板外定义其成员时,必须记住,我们并不在类的作用域中,直到遇到类名才表示进入类的作用域。
|
|
类模板完整示例代码:
|
|
在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板参数。
类模板和友元(暂时不看)
类模板的static成员
与任何其他类相同,类模板可以声明static成员
|
|
每个Foo的实例都有其自己的static成员实例。即,对于任意给定类型X,都有一个Foo
我们将static数据成员也定义为模板:
|
|
访问类模板的static成员
|
|
类似于任何其他成员函数,一个static成员函数只有在使用时才会实例化。