面向对象编程和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型了
一个泛型程序是独立于任何特定类型来编写代码的
模板是泛型编程的基础
一、定义模板
一个函数模板就是一个公式,可用来生成针对特定类型的函数版本
模板定义以关键字template开始,后跟一个模板参数的列表,这是一个逗号分隔的一个或多个模板参数的列表,用尖括号包围起来。在模板定义中,模板参数列表不能为空
模板参数表示在类或函数定义中用到的类型或值
当使用模板时,我们(隐式或显式)指定模板实参,将其绑定到模板参数上
当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参。编译器用推断出的模板参数来为我们实例化一个特定版本的函数
模板类型参数,一般来说可以将模板类型参数当作类型说明符来使用。所以类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换
类型参数前必须使用关键字class或typename,在模板参数列表中,这两个关键字的含义相同,typename更直观,但是有些旧的编译器只支持class。这两个关键字可以混合使用
我们通过特定的类型名而非关键字class或typename来指定非类型参数
当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所替代,这些值必须是常量表达式,从而允许编译器在编译时实例化模板
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或左值引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期(你懂的)。指针参数也可以用nullptr或一个值为0的常量表达式来实例化
在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数
函数模板可以为inline或constexpr的。注意关键字放置的位置
编写泛型代码的两个重要原则:模板中的函数参数是对const的引用;函数体中的条件判断仅使用<比较运算
模板程序应该尽量减少对实参类型的要求
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。当我们使用(而不是定义,是实例化)模板时,编译器才生成代码,这一特性影响了我们如何组织代码以及错误何时被检测到
通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中(两者间的关系怎么维持)
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义
函数模板和类模板成员函数的定义通常放在头文件中
通过组织良好的程序结构,恰当的使用头文件,这些要求都很容易满足。模板的设计者都应该提供一个头文件,包含模板定义以及在类模板或成员定义中用到的所有名字的声明。模板的用户必须包含模板的头文件,以及用来实例化模板的任何类型的头文件
大多数编译错误在实例化期间报告。保证传递给模板的参数支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。模板对所使用的类型有一些假设,传给模板的实参要满足这些假设
编译器不能为类模板推断模板参数类型
当使用一个类模板时,我们必须提供额外信息。那些额外信息就是显式模板实参列表,他们被绑定到模板参数。编译器使用这些模板实参来实例化出特定的类
一个类模板的每个实例都形成一个独立的类,不同的实例之间完全没有关联,也不存在特殊的访问权限
在模板作用域中引用模板类型
我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数
类模板的成员函数本身是一个普通函数。但是,但类模板的每个实例都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数。因而,定义在类模板之外的成员函数就必须以关键字template开始,后接类模板参数列表
注意在类模板外定义普通成员函数的格式p586
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化
当我们使用一个类模板类型时必须提供模板实参,但这一规则有一个例外。在类模板自己的作用域中,我们可以直接使用模板名而不提供实参(例如函数返回模板自身的引用时)。在类模板之外使用必须提供实参
类模板和友元:一对一友好关系;通用和特定的模板友好关系p589,注意不需要前置声明的情况
令模板自己的类型参数成为友元
模板类型别名:template using twin = pair<T, T>
类模板的static成员:每一个实例拥有独立的static成员
与定义类模板的成员函数类似,我们将static数据成员也定义为模板
与非模板类的静态成员相同,我们可以通过类类型对象来访问一个类模板的static成员,也可以使用作用域运算符直接访问成员(通过一个特定的类模板实例)
一个static成员函数只有在使用时才会实例化(存储位置???)
模板参数遵循普通的作用域规则。一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。与任何其他的名字一样,模板参数会隐藏外层作用域中声明的相同名字。在模板内不能重用模板参数名
模板声明必须包含模板参数,一个给定模板的每个声明和定义必须有相同数量和种类(类型或非类型)的参数
一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前
默认情况下,C++语言假定通过作用域运算符访问的名字不是类型(与一般用法不同呀)。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点。与一般用法不同
当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不使用class
在新标准中,我们可以为函数模板和类模板提供默认模板实参
作为泛型算法函数中用作比较的可调用对象的返回类型必须能转换为bool值,且接受的形参类型必须与泛型算法函数中前两个实参类型兼容
与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参
类模板中也可以定义默认模板实参,规则大同小异p594
一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板。成员模板不能是虚函数
普通类的成员模板p595
类模板的成员模板,在此情况下,类和成员各自有自己的、独立的模板参数
当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表
为了实例化一个类模板的成员模板,我们必须同时提供类和函数模板的实参。与往常一样,我们在哪个对象上调用成员模板,编译器就根据该对象的类型来推断类模板参数的实参。与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参
当模板被使用时才会进行实例化,这意味着,相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板,并提供了相同的模板实参时,每个文件中就都会有该模板的一个实例
在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显式实例化来避免这种开销
extern template declaration;//实例化声明
template declaration;//实例化定义
declaration是一个类或函数声明,其中所有模板参数已被替换为模板实参
当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义
由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化版本的代码之前。这两种语句一般都放在文件的开头处
实例化定义会实例化所有成员,所以所用的类型必须能够用于类模板的所有成员
普通实例化只会实例化用到的成员,所用的类型不一定必须要能够用于类模板的所有成员
效率与灵活性p599
二、模板实参推断
从函数实参来确定模板实参的过程被称为模板实参推断
类型转换与模板类型参数p601
函数模板显式实参p603
尾置返回类型与类型转换p604
函数指针和实参推断p607
模板实参推断和引用p608
理解std::move p611
转发p612
三、重载与模板
四、可变参数模板
编写可变参数函数模板p620
包扩展p621
转发参数包p622