C++ Primer 第五版 第16章 模板与泛型编程

news2025/1/11 1:54:46

模板是C++中泛型编程的基础。一个模板就是一个创建类或函数的蓝图或者说公式。当使用一个vector这样的泛型类型,或者find这样的泛型函数时,我们提供足够的信息,将蓝图转换为特定的类或函数。这种转换发生在编译时。

一、定义模板
1. 函数模板

一个函数模板(function template)就是一个公式,可用来生成针对特定类型的函数版本。

模板定义以关键字template开始,后跟一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数的列表,用小于号(<)和大于号(>)包围起来。

在模板定义中,模板参数列表不能为空。

模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式地或显式地)指定模板实参(template argument),将其绑定到模板参数上。

实例化函数模板

当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板实参。

编译器用推断出地模板参数来为我们实例化(instantiate)一个特定版本的函数。当编译器实例化一个模板时,它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”。

模板类型参数

类型参数可以用来指定返回类型或函数的参数类型,以及在函数体内用于变量声明或类型转换:

类型参数前必须使用关键字class或typename:

非类型模板参数

在模板中也可以定义非类型参数。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定非类型参数。

当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。

在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数。例如,指定数组大小。

inline和constexpr的函数模板

模板编译

当我们使用(而不是定义)模板时,编译器才生成代码。

大多数编译错误在实例化期间报告

2. 类模板

类模板是用来生成类的蓝图的。与函数模板的不同之处是,编译器不能为类模板推断模板参数类型。为了使用类模板,我们必须在类模板名后的尖括号中提供额外信息——用来代替模板参数的模板实参列表。

实例化类模板

当使用一个类模板时,我们必须提供额外信息。这些额外信息是显式模板实参列表,它们被绑定到模板参数。编译器使用这些模板参数来实例化出特定的类。

类模板的成员函数

默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。

在类代码内简化模板类名的使用

当我们使用一个类模板类型时必须提供模板实参,但这一规则有一个例外。在类模板自己的作用域中,我们可以直接使用模板名而不提供实参。

在一个类模板的作用域内,我们可以直接使用模板名而不必指定模板实参。

类模板和友元

当一个类包含一个友元声明时,类与友元各自是否是模板是相互无关的。如果一个类模板包含一个非模板友元,则友元被授权可以访问所有模板实例。如果友元本身是模板,类可以授权给所有友元模板实例,也可以只授权给特定实例。

一对一友好关系

类模板与另一个(类或函数)模板间友好关系的最常见的形式是建立对应实例及其友元间的友好关系。

通用和特定的模板友好关系

令模板自己的类型参数成为友元

在新标准中,我们可以将模板类型参数声明为友元:

模板类型别名

类模板的一个实例定义了一个类类型,与任何其他类类型一样,我们可以定义一个typedef来引用实例化的类:

typedef Blob<string> StrBlob;

我们可以为类模板定义一个类型别名:

template<typename T> using twin = pair<T, T>;
twin<string> authors;  // authors是一个pair<string, string>

类模板的static成员

与任何其他static数据成员相同,模板类的每个static数据成员必须有且仅有一个定义。但是,类模板的每个实例都有一个独有的static对象。

3. 模板参数
模板参数与作用域

模板参数遵循普通的作用域规则。但与大多数其他上下文不同,在模板内不能重用模板参数名。

模板声明

模板声明必须包含模板参数。

与函数参数相同,声明中的模板参数的名字不必与定义中相同。

一个特定文件所需要的所有模板的声明通常一起放置在文件开始位置,出现于任何使用这些模板的代码之前。

使用类的类型成员

默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点。

默认模板实参

我们可以为函数和类模板提供默认实参。

在这段代码中,我们为模板添加了第二个类型参数,名为F,表示可调用对象的类型;并定义了一个新的函数参数f,绑定到一个可调用对象上。

默认模板实参指出compare将使用标准库的less函数对象类,它是使用与compare一样的类型参数实例化的。默认函数实参指出f将是类型F的一个默认初始化的对象。

与函数默认实参一样,对于一个模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。

模板默认实参与类模板

无论何时使用一个类模板,我们都必须在模板名之后接上尖括号。如果一个类模板为其所有模板参数都提供了默认实参,且我们希望使用这些默认实参,就必须在模板名之后跟一个空尖括号对。

4. 成员模板

一个类(无论是普通类还是类模板)可以包含本身是模板的成员函数。这种成员被称为成员模板(member template)。成员模板不能是虚函数。

普通(非模板)类的成员模板

类模板的成员模板

实例化与成员模板

为了实例化一个类模板的成员模板,我们必须同时提供类和函数模板的实参。与普通函数模板相同,编译器通常根据传递给成员模板的函数实参来推断它的模板实参。

5. 控制实例化

在大系统中,在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中,我们可以通过显式实例化(explicit instantiation)来避免这种开销。一个显式实例化有如下形式:

实例化定义会实例化所有成员

6. 效率与灵活

在运行时绑定删除器

在编译时绑定删除器

二、模板实参推断

从函数实参来确定模板实参的过程被称为模板实参推断(template argument deduction)。

1. 类型转换与模板类型参数

顶层const无论是在形参中还是在实参中,都会被忽略。在其他类型转换中,能在调用中应用于函数模板的包括如下两项。

· const转换:可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参。

· 数组或函数指针转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。

其他类型转换,如算术转换、派生类向基类的转换以及用户定义的转换,都不能应用于函数模板。

fref调用不合法。如果形参是一个引用,则数组不会转换为指针。a和b的类型是不匹配的,因此调用是错误的。

使用相同模板参数类型的函数形参

一个模板类型参数可以用作多个函数形参的类型。由于只允许有限的几种类型转换,因此传递给这些形参的实参必须具有相同的类型。如果推断出的类型不匹配,则调用就是错误的。

正常类型转换应用于普通函数实参

函数模板可以有用普通类型定义的参数,即,不涉及模板类型参数的类型。这种函数实参不进行特殊处理:它们正常转换为对应形参的类型。

2. 函数模板显式实参

在某些情况下,编译器无法推断出模板实参的类型。其他一些情况下,我们希望允许用户控制模板实例化。当函数返回类型与参数列表中任何类型都不相同时,这两种情况最常出现。

指定显式模板实参

正常类型转换应用于显式指定的实参

3. 尾置返回类型与类型转换

进行类型转换的标准库模板类

标准库的类型转换(type transformation)模板定义在头文件type_trait中。这个头文件中的类通常用于所谓的模板元程序设计。

如果不可能(或者不必要)转换模板参数,则type成员就是模板参数类型本身。例如,如果T是一个指针类型,则remove_pointer<T>::type是T指向的类型。如果T不是一个指针,则无须进行任何转换,从而type具有与T相同的类型。

4. 函数指针和实参推断

当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参。

5. 模板实参推断和引用
从左值引用函数参数推断类型

当一个函数参数是模板类型参数的一个普通(左值)引用时(即,形如T&),绑定规则告诉我们只能传递给它一个左值(如,一个变量或一个返回引用类型的表达式)。

从右值引用函数参数推断类型

引用折叠和右值引用参数

假定i是一个int对象。

C++在正常绑定规则之外定义了两个例外规则。

第一个例外规则:当我们将一个左值(如i)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。因此当我们调用f3(i)时,编译器推断T的类型为int&,而非int。

第二个例外绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了“折叠”。在所有情况下(除了一个例外),引用会折叠成一个普通的左值引用类型。在新标准中,折叠规则扩展到右值引用。只在一种特殊情况下引用会折叠成右值引用:右值引用的右值引用。即,对于一个给定类型X:

· X& &、 X& && 和X&& &都折叠成类型X&

· 类型X&& &&折叠成X&&

编写接受右值引用参数的模板函数

在实际中,右值引用通常用于两种情况:模板转发其实参或模板被重载。

使用右值引用的函数模板通常使用以下方式来进行重载:

6. 理解std::move

虽然不能直接将一个右值引用绑定到一个左值上,但可以用move获得一个绑定到左值上的右值引用。

std::move是如何定义的

标准库是这样定义move的:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t);
}

move的函数参数T&&是一个指向模板类型参数的右值引用。通过引用折叠,此参数可以与任何类型的实参匹配。特别地,我们既可以传递给move一个左值,也可以传递给它一个右值:

string s1("hi!"), s2;
s2 = std::move(string("bye!"));  // 正确:从一个右值移动数据
s2 = std::move(s1);  // 正确:但在赋值之后,s1的值是不确定的

从一个左值static_cast到一个右值引用是允许的

虽然不能隐式地将一个左值转换为右值引用,但我们可以用static_cast显式地将一个左值转换为一个右值引用。

7. 转发

某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在此情况下,我们需要保持被转发实参的所有性质,包括实参类型是否是const的以及实参是左值还是右值。

定义能保持类型信息的函数参数

在调用中使用std::forward保持类型信息

三、重载与模板

重载模板和类型转换

f(p)的调用与预期不符,预期调用f(const T*), 实际调用f(T)。分析原因:调用f(const T*)还需要进行const转换,而调用f(T)实例化为f(int*)。

四、可变参数模板

一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(template packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包(function parameter packet),表示零个或多个函数参数。

我们用一个省略号来指出一个模板参数或函数参数表示一个包。再一个模板参数列表中,class...或typename...指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

与往常一样,编译器从函数的实参推断模板参数类型。对于一个可变参数模板,编译器还会推断包中参数的数目。

sizeof...运算符

当我们需要知道包中有多少元素时,可以使用sizeof...运算符。类似sizeof,sizeof...也返回一个常量表达式,而且不会对其实参求值。

1. 编写可变参数函数模板

我们可以使用initializer_list来定义一个可接受可变数目实参的函数。但是,所有实参必须具有相同的类型(或者它们的类型可以转换为同一个公共类型)。当我们既不知道想要处理的实参的数目也不知道它们的类型时,可变参数函数是很有用的。

可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身。

2. 包扩展

扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式右边放置一个省略号(...)来触发扩展操作。

3. 转发参数包

五、模板特例化

当我们不能(或不希望)使用模板版本时,可以定义类或函数模板的一个特例化版本。

定义函数模板特例化

当我们特例化一个函数模板时,必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板,应使用关键字template后跟一个空尖括号对(<>)。空尖括号指出我们将为原模板的所有模板参数提供实参:

当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配。

一个特例化版本本质上是一个实例,而非函数名的一个重载版本。特例化不影响函数匹配。

类模板特例化

举例:为标准库hash模板定义一个特例化版本,可以用它来将Sales_data对象保存在无序容器中。默认情况下,无序容器使用hash<key_type>来组织元素。为了让我们自己的数据类型也能使用这种默认组织方式,必须定义hash模板的一个特例化版本。一个特例化hash类必须定义:

在定义此特例化版本的hash时,唯一复杂的地方是:必须在原模板定义所在的命名空间中特例化它。我们可以向命名空间添加成员。

// 打开std命名空间,以便特例化std::hash
// 花括号对之间的任何定义都将成为命名空间std的一部分
namespace std {
template <>  //  我们正在定义一个特例化版本,模板参数为Sales_data
struct hash<Sales_data>
{
    // 用来散列一个无序容器的类型必须要定义以下类型
    typedef size_t result_type;
    typedef Sales_data argument_type;  // 默认情况下,此类型需要==
    size_t operator()(const Sales_data& s) const;
    //  我们的类使用合成的拷贝控制成员和默认构造函数
};
size_t
hash<Sales_data>::operator() (const Sales_data& s) const
{
    return hash<string>()(s.bookNo) ^ hash<unsigned>() (s.unsigned) ^ hash<double>()(s.revenue);
}
}  // 关闭std命名空间

类模板部分特例化

与函数模板不同,类模板的特例化不必为所有模板参数提供实参,我们可以只指定一部分而非所有模板参数,或是参数的一部分而非全部特性。一个类模板的部分特例化(partial specialization)本身是一个模板,使用它时用户还必须为那些在特例化版本中为指定的模板参数提供实参。

特例化成员而不是类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1820438.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OceanBase 并行执行参数 parallel_servers_target 理解

为了最大程度降低 PX 使用难度&#xff0c;OceanBase 3.1 版起&#xff0c;parallel_max_servers 参数废弃。 用户只需用好 parallel_servers_target 即可。 target 的用途 用一个酒吧的例子来粗略理解下 parallel_servers_target 的意思&#xff1a; target 先生开了一个酒…

Epicor BAQ - BAQ设计与调用

目录 一、BAQ设计常用功能1.跨公司查询2.修改作者3.添加筛选条件4.使用BAQ参数5.子查询 二、在客制化中调用BAQ取数三、在BPM中调用BAQ取数四、结束 一、BAQ设计常用功能 1.跨公司查询 在BAQ的General页面勾选Cross-company后&#xff0c;BAQ可以跨公司查询数据。 2.修改作…

联邦学习论文阅读:2018 Federated learning with non-IID data

介绍 这是一篇2018年挂在arXiv上的文章&#xff0c;是一篇针对FL中数据Non-IID的工作。 作者发现&#xff0c;对于高度Non-IID的数据集&#xff0c;FedAvg的准确性下降了55%。 作者提出了可以用权重散度&#xff08;weight divergence&#xff09;来解释这种性能下降&#xff…

基于JSP技术的大学生校园兼职系统

开头语 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;可以通过文末的联系方式找到我。 开发语言 JSP 数据库 MySQL 技术 JSP JavaBeans 工具 MyEclipse、Tomcat、Navicat 系统展示 首页 学生登录界面 招聘信息界面 论坛中心界面 摘…

时间类:Calendar

一.Calendar概述 1.Calendar代表了系统当前时间的日历对象,可以单独修改,获取时间中的年&#xff0c;月&#xff0c;日 2.细节:Calendar是一个抽象类,不能直接创建对象。 二.获取Calendar日历类对象的方法 // 会根据系统的不同时区来获取不同的日历对象 // 会根据系统的不同…

LLM的7种推理框架

我们如何在本地安全地运行私有的LLMs呢&#xff1f;开源模型为此提供了可能的解决方案。本文将介绍七种方法。 Hugging Face的transformers 这是一个Python库&#xff0c;可以简化本地运行LLM的过程。 Transformers的优点&#xff1a; 自动模型下载提供代码片段非常适合实验…

C语言的数据结构:串、数组、广义表

一、串 1、串的定义 串是一个线性表&#xff0c;但其节点中的内容只能为字符&#xff0c;所以也称为字符串。 字符串中可以有多个字符&#xff0c;也可以没有字符。没有字符的叫作&#xff1a;空串。 空串&#xff1a;""。 有值的串&#xff1a;"1123"。 只…

“AI 热会逐渐降温,AGI 普及不了多少场景!”对话《Core Java》作者 Cay Horstmann...

作者 | 王启隆 责编 | 唐小引 出品丨AI 科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 已过花甲之年的 Cay Horstmann 是 Java 经典著作《Java 核心技术》和《Java 核心技术&#xff1a;速学版》的作者&#xff0c;帮助了无数 Java 开发者启蒙进阶。截止到今天&a…

3d数字家居展馆线上制作工具更具创意

立足于引领未来展览新潮流的出发点&#xff0c;深圳华锐视点3D云展厅依托前沿的Web3D技术和vr全景制作技术&#xff0c;提供Web3D在线创意展厅搭建编辑器&#xff0c;为您打造一个突破时空限制、风格多样的线上展厅。 Web3D在线创意展厅搭建编辑器将您的产品以三维模型的形式生…

MultiTrust:首个综合统一的多模态信任度基准(上)

随着我们迈向人工通用智能&#xff08;AGI&#xff09;的时代&#xff0c;出现了开创性的大语言模型&#xff08;LLMs&#xff09;。凭借它们强大的语言理解和推理能力&#xff0c;已经无缝地将其他模态&#xff08;例如视觉&#xff09;整合到LLMs中&#xff0c;以理解不同的输…

创建节约机关怎样向媒体投稿报道宣传?

创建节约机关并向媒体投稿报道宣传是一项重要的工作&#xff0c;它不仅能够提升机关的形象&#xff0c;还能促进社会各界对节约型社会的认识和支持。 作为一名新晋信息宣传员,初入职场的我满腔热血,怀揣着用文字传递价值的理想,却在投稿的道路上屡遭波折。面对每月的宣传任务,我…

LLM-不要错过,教你如何快速且精准生成提示词?(总结Singapore首届GPT-4提示工程获奖者Sheila Teo博客)

文章目录 前置理论精炼介绍1. CO-STAR框架CO-STAR框架简单介绍CO-STAR简单示例 2. 创建系统提示【优化LLM问答丰富度】何为系统提示&#xff1f;系统提示示例 3. 使用分隔符分段提示【优化问答准度】分割符作特殊字符及CO-STAR示例分割符作XML标记 仅数据的CO-STAR实操前置分析…

LVS/NAT负载均衡实操

添加规则,并做持久操作 1 添加规则 [rootlvs ~]# ipvsadm -A -t 10.36.178.183:80 -s wrr [rootlvs ~]# ipvsadm -a -t 10.36.178.183:80 -r 192.168.65.201:80 -m -w 3 [rootlvs ~]# ipvsadm -a -t 10.36.178.183:80 -r 192.168.65.202:80 -m -w 1[rootlvs ~]# ipvsadm -Ln …

告别“人治”时代,物业运维平台能否成为行业新标准?

随着数字化时代的飞速发展&#xff0c;智能化、数字化已经遍及所有的行业。物业服务企业也不例外&#xff0c;你是否还在想象物业运维工作依旧停留在手动报修、纸质记录的古老时代&#xff1f;那么&#xff0c;你就OUT了&#xff0c;物业运维平台已经悄然崛起&#xff0c;正在以…

悦库企业网盘 /user/login/.html SQL注入漏洞复现

0x01 产品简介 悦库企业网盘是一款专为满足企业文件管理、协同办公、文件共享需求而设计的私有部署安全、简单的企业文件管理系统。该产品全面覆盖企业文件管理场景,提供一系列功能强大且操作简便的解决方案,助力企业提升效率、降低管理成本。悦库企业网盘提供精细的权限管理…

汉明校验·简明教程

汉明校验简明教程 一、简介 汉明码是由 Richard Hanming 于 1950 年提出的&#xff0c;它具有一位纠错能力。 新增的汉明码校验位数应满足如下关系&#xff1a; 2 k ⩾ n k 1 2^{k}\geqslant nk1 2k⩾nk1&#xff0c;其中k为校验位位数&#xff0c;n位数据位数。 同时&…

【ARMv8/ARMv9 硬件加速系列 1 -- SVE | NEON | SIMD | VFP | MVE | MPE 基础介绍】

文章目录 ARM 扩展功能介绍VFP (Vector Floating Point)SIMD (Single Instruction, Multiple Data)NEONSVE (Scalable Vector Extension)SME (Scalable Matrix Extension)CME (Compute Matrix Engine)MVE (M-profile Vector Extension)MPE (Media Processing Engine)总结 ARM 扩…

首件检验为什么这么重要?

首件检验是制造业生产过程中的一个重要环节&#xff0c;通常是在每个班次刚开始时或生产过程中的条件发生改变后&#xff08;如人员变动、材料更换、设备调整等&#xff09;&#xff0c;对加工的第一或前几件产品进行的专门检验。尤其在汽车零部件生产企业、电子制造企业广泛采…

分布式协议之巅 — 揭秘基础Paxos与Raft协议如何实现分布式系统达成一致性(非变种Paxos协议)

揭秘Paxos与Raft协议如何实现分布式系统达成一致性 前提介绍Paxos专题大纲Paxos协议Paxo协议的角色标准Paxos角色Proposer&#xff08;提案者&#xff09;Acceptor&#xff08;接受者&#xff09;Learner&#xff08;学习者&#xff09; 提案编号与确认值的组合解析Paxos协议的…

C++ 25 之 调用函数调用规则

c25调用函数调用规则.cpp #include<iostream> using namespace std;class Students04{ // 1.创建好类之后&#xff0c;编译器会默认提供三个函数&#xff1a;默认构造函数、构造函数、拷贝构造函数 // 2.自己写了有参构造函数&#xff0c;编译器就不会提供默认构造函数&…