【C++】《C++ Primer 5th》笔记-Chapter16-模板与泛型编程

笔记:
一、定义模板
1、面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况;而在泛型编程中,在编译时就能获知类型了。
2、模板是C++泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公式。
3、在模板定义中,模板参数列表不能为空。在模板参数列表中,typename和class没有什么不同。
4、当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参。
5、编译器用推断出的模板参数来为我们实例化一个特定版本的函数。当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新"实例"。
6、类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换。
7、当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
一个非类型参数可以是一个整型,或者是一个指向对象或函数类型的指针或(左值)引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态的生存期。我们不能用一个普通(非static)局部变量或动态对象作为指针或引用非类型模板参数的实参。指针参数也可以用nullptr或一个值为0的常量表达式来实例化。
8、非类型模板参数的模板实参必须是常量表达式。
9、函数模板可以声明为inline或constexpr的,如同非模板函数一样。inline或constexpr说明符放在模板参数列表之后,返回类型之前。
10、模板程序应该尽量减少对实参类型的要求。
11、当编译器遇到一个模板定义时,它并不会生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
12、通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包含声明也包括定义。
函数模板和类模板成员函数的定义通常放在头文件中。
13、模板的设计者应该提供一个头文件,包含模板定义以及在类模板或成员定义中用到的所有名字的声明。模板的用户必须包含模板的头文件,以及用来实例化模板的任何类型的头文件。
14、保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。
15、与函数模板的不同之处是,编译器不能为类模板推断模板参数类型。
16、一个类模板的每个实例都形成一个独立的类。
17、与其他任何类相同,我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数。
18、默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。
如果一个成员函数没有被使用,则它不会被实例化。成员函数只有在被用到时才进行实例化,这一特性使得即使某种类型不能完全符合模板操作的要求,我们仍然能用该类型实例化类。
默认情况下,对于一个实例化了的类模板,其成员只有在使用时才被实例化。
19、当我们使用一个类模板类型时必须提供模板实参,但这一规则有一个例外。在类模板自己的作用域中,我们可以直接使用模板名而不提供实参。
当我们在类模板外定义其成员时,必须记住,我们并不在类的作用域中,直到遇到类名才表示进入类的作用域。
20、当一个类包含一个友元声明时,类与友元各自是否是模板是相互无关的。如果一个类模板包括一个非模板友元,则友元被授权可以访问所有模板实例。如果友元自身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。
21、类模板与另一个(类或函数)模板间友好关系的最常见的形式是建立对应实例及其友元间的友好关系。
22、为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。
23、在新标准中,我们可以将模板类型参数声明为友元。
24、类似任何其他成员函数,一个static成员函数只有在使用时才会实例化。
25、一个模板参数名的可用范围是在其声明之后,至模板声明或定义结束之前。
注意,在模板内不能重用模板参数名。一个模板参数名在一个特定模板参数列表中只能出现一次。
26、模板声明必须包含模板参数。
一个给定模板的每个声明和定义必须有相同数量和种类(即,类型或非类型)的参数。
27、一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。
28、默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。
当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。
29、我们也可以提供默认模板实参。
与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。
30、一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板。成员模板不能是虚函数。
31、当我们在类模板外定义一个成员模板时,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的模板参数列表。
32、在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显式实例化来避免这种开销。例如:
extern template class Blob<String>;                // 声明
template int compare(const int&, const int&);    // 定义
当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。
由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化版本的代码之前。
实例化文件必须为每个在其他文件中声明为extern的类型和函数提供一个(非extern)的定义。
对每个实例化声明,在程序中某个位置必须有其显式的实例化定义。

33、一个类模板的实例化定义会实例化该模板的所有成员,包括内联的成员函数。
因此,我们用来显示实例化一个类模板的类型,必须能用于模板的所有成员。
34、通过在编译时绑定删除器,unique_ptr避免了间接调用删除器的运行时开销。通过在运行时绑定删除器,shared_ptr使用户重载删除器更为方便。

二、模板实参推断
1、与往常一样,顶层const无论是在形参中还是在实参中,都会被忽略。在其他类型转换中,能在调用中应用于函数模板的包括如下两项:
①const转换:可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参。
②数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。
其他类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。
将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。

2、如果函数参数类型不是模板参数,则对实参进行正常的类型转换。
3、提供显式模板实参的方式与定义类模板实例的方式相同,显式模板实参在尖括号中给出,位于函数名之后,实参列表之前。
4、当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,能唯一确定其类型或值。
5、引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数。
6、如果一个函数参数是指向模板参数类型的右值引用(如,T&&),则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用(T&)。
7、虽然不能直接将一个右值引用绑定到一个左值上,但可以用move获得一个绑定到左值上的右值引用。
8、虽然不能隐式地将一个左值转换为右值引用,但我们可以用static_cast显式地将一个左值转换为一个右值引用。
9、如果一个函数参数是指向模板类型参数的右值引用(如,T&&),它对应的实参的const属性和左值/右值属性将得到保持。
10、我们可以使用一个名为forward的新标准库设施来保持原始实参的类型。与move不同,forward必须通过显式模板实参来调用。forward返回该显式实参类型的右值引用。即,forward<T>的返回类型是T&&。
通常情况下,我们使用forward传递那些定义为模板类型参数的右值引用的函数参数。通过其返回类型上的引用折叠,forward可以保持给定实参的左值/右值属性。
当用于一个指向模板参数类型的右值引用函数参数(T&&)时,forward会保持实参类型的所有细节。

三、重载与模板
1、函数模板可以被另一个模板或一个普通非模板函数重载。
2、如果涉及函数模板,则函数匹配规则会在以下几方面受到影响:
①对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
②候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
③与往常一样,可行函数(模板与非模板)按类型转换(如果对此调用需要的话)来排序。当然,可以用于函数模板调用的类型转换是非常有限的。
④与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是,如果有多个函数提供同样好的匹配,则:
——如果同样好的函数中只有一个是非模板函数,则选择此函数。
——如果同样好的函数中没有模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。
——否则,此调用有歧义。

3、在定义任何函数之前,记得声明所有重载的函数版本。这样就不必担心编译器由于未遇到你希望调用的函数而实例化一个并非你所需的版本。

四、可变参数模板
1、一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。
可变数目的参数被称为参数包。
2、我们用一个省略号来指出一个模板参数或函数参数表示一个包。
3、当我们需要知道包中有多少元素时,可以使用sizeof...运算符。
4、可变参数函数通常是递归的。
5、扩展中的模式会独立地应用于包中的每个元素。

五、模板特例化
1、一个特例化版本本质上是一个实例,而非函数名的一个重载版本。
因此,特例化不影响函数匹配。
2、为了特例化一个模板,原模板的声明必须在作用域中。而且,在任何使用模板实例的代码之前,特例化版本的声明也必须在作用域中。
模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
3、一个类模板的部分特例化本身是一个模板,使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
我们只能部分特例化类模板,而不能部分特例化函数模板。
4、我们可以只特例化特定成员函数而不是特例化整个模板。

一些术语:
1、模板特例化:类模板、类模板的成员或函数模板的重定义,其中指定了某些(或全部)模板参数。模板特例化版本必须出现在原模板的声明之后,必须出现在任何利用特殊实参来使用模板的代码之前。一个函数模板中的每个模板参数都必须完全特例化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值