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

模板是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.mzph.cn/web/26507.shtml

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

相关文章

windows11 建立批处理bat文件来删除指定目录下的所有隐藏的文件。

今天在导入项目的时候发现之前项目中的文件夹中有很多隐藏的临时文件&#xff0c;这个文件应该是版本控制产生的&#xff0c;导致导入后文件夹上有X&#xff0c;然后里面文件是一个没有错。 我们来建立一个bat来&#xff0c;进行批量删除隐藏文件就可以了&#xff1a; echo o…

安装前端与Web前端:深度探索与实践

安装前端与Web前端&#xff1a;深度探索与实践 在数字化时代&#xff0c;前端技术作为用户界面的直接呈现者&#xff0c;其重要性不言而喻。然而&#xff0c;对于初学者而言&#xff0c;安装前端与Web前端的过程往往充满了困惑与挑战。本文将从四个方面、五个方面、六个方面和…

[12] 使用 CUDA 进行图像处理

使用 CUDA 进行图像处理 当下生活在高清摄像头的时代,这种摄像头能捕获高达1920*1920像素的高解析度画幅。想要实施的处理这么多的数据,往往需要几个TFlops地浮点处理性能,这些要求CPU也无法满足通过在代码中使用CUDA,可以利用GPU提供的强大地计算能力CUDA支持多维地Grid和…

鸿蒙开发电话服务:【@ohos.contact (联系人)】

联系人 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import contact from ohos.contact;contact.addContact addContact(contact:Contact, callback:AsyncCallback): void 添加联…

纯C实现的ymodem库,无额外依赖

本文目录 1、引言2、理论2.1 YMODEM协议的主要特点2.2 YMODEM的工作原理 3、代码3.1 main.cpp3.2 ymodem.c 3.3 ymodem.h 4、验证4.1 ymodem发送4.2 ymodem接收 5、移植说明 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我…

源码解析:从零解读SAM(Segment Anything Model)大模型!

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

TF-IDF算法教程

前言 TF-IDF&#xff08;Term Frequency-Inverse Document Frequency&#xff09;是一种常用的文本分析技术&#xff0c;广泛应用于信息检索和文本挖掘领域。它是一种统计方法&#xff0c;用于评估一个词语在一个文档中的重要程度。TF-IDF的核心思想是&#xff1a;如果一个词语…

在 Windows 操作系统中,可以通过命令行工具来杀死进程

1. 使用 taskkill 命令 taskkill 命令是一个用于终止进程的命令行工具&#xff0c;可以通过进程名称或进程 ID (PID) 来杀死进程。 按进程名称杀死进程 taskkill /IM processname.exe /Fprocessname.exe 是进程的名称。/F 参数表示强制终止进程。 例如&#xff0c;终止名为…

VS2019+QT5.15调用动态库dll带有命名空间

VS2019QT5.15调用动态库dll带有命名空间 vs创建动态库 参考&#xff1a; QT调用vs2019生成的c动态库-CSDN博客 demo的dll头文件&#xff1a; // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 DLL3_EXPORTS // 符号…

联邦学习的基本流程,联邦学习权重聚合,联邦学习权重更新

目录 联邦学习的基本流程是 S_t = np.random.choice(range(K), m, replace=False) 联邦学习权重聚合 model.state_dict() 联邦学习权重更新 下载数据集 https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz 联邦学习的基本流程是 **1. server初始…

四十一、openlayers官网示例Flight Animation解析——在地图上绘制飞机航线、牵引线效果、动态动画

官网demo地址&#xff1a; Flight Animation 这篇介绍了如何实现飞机航线动画。 首先加载一张底图&#xff0c;定义一个样式。 const tileLayer new TileLayer({source: new StadiaMaps({layer: "outdoors",}),});const map new Map({layers: [tileLayer],target…

【实例分享】访问后端服务超时,银河麒麟服务器操作系统分析及处理建议

1.服务器环境以及配置 【机型】 处理器&#xff1a; Intel 32核 内存&#xff1a; 128G 整机类型/架构&#xff1a; x86_64虚拟机 【内核版本】 4.19.90-25.22.v2101.kylin.x86_64 【OS镜像版本】 kylin server V10 SP2 【第三方软件】 开阳k8s 2.问题现象描述 …

富格林:细心发现虚假确保安全

富格林指出&#xff0c;现货黄金市场内蕴藏着丰富的盈利机会&#xff0c;然而并非所有人都能够抓住这些机会。要想从市场中获取丰厚的利润并且保障交易的安全&#xff0c;必须要求我们掌握一些交易技巧利用此去发现虚假陷阱。当我们不断汲取技巧过后&#xff0c;才可利用此来发…

API工具--Apifox和Postman对比(区别)

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

【学习笔记】Linux

Linux 1、 介绍 1.1、概述 1.2、特点 1.3、Linux的发行版2、 基础篇 —— 文件系统 2.1、文件系统 2.2、目录结构3、 基础篇 —— VI/VIM 编辑器 3.1、概述 3.2、编辑器模式及常用命令4、 基础篇 —— 网络配置 4.1、VMware NetWork …

供应链初学者手册——第七部分:全球供应链管理

供应链初学者手册 文章目录 供应链初学者手册第七部分&#xff1a;全球供应链管理14. 全球供应链的特征14.1 全球供应链的优势和挑战14.2 跨国供应链管理策略 15. 国际物流与供应链管理15.1 国际物流管理 15. **国际物流与供应链管理**15.1 国际物流管理15.2 全球供应链风险管理…

探索重要的无监督学习方法:K-means 聚类模型

在数据科学和机器学习领域,聚类分析是一种重要的无监督学习方法,用于将数据集中的对象分成多个组(簇),使得同一簇中的对象相似度较高,而不同簇中的对象相似度较低。K-means 聚类是最广泛使用的聚类算法之一,它以其简单、快速和易于理解的特点受到了广泛关注。本文将深入…

Linux so文件无法找到及某条命令找不到的解决办法

前言 在一些定制软件中可能会自带so文件。或者自带一些二进制命令。 这时会如果运行某个程序会发生 **.so 文件无法找到的错误。 以及 * 某条命令无法找到的错误。 比如像是下面这样 解决办法&#xff1a; so文件无法找到 通过往 LD_LIBRARY_PATH 变量中追加路径来告诉程序…

Linux中FTP安装

文章目录 一、FTP介绍1.1、FTP是什么1.2、FTP的工作原理1.3、FTP的传输模式1.4、FTP用户类别1.5、FTP的优点与缺点1.6、FTP数据传输格式 二、FTP客户端与服务端2.1、服务端2.2、客户端 三、FTP服务器软件介绍3.1、WU-FTPD3.2、ProFtpD3.3、vsftpd3.4、Pure-FTP3.5、FileZilla S…

使用 zkCli 操作 ZooKeeper 节点

创建一个新节点&#xff1a; create /example "Hello, ZooKeeper!"列出根目录下的节点&#xff1a; ls /获取节点的数据&#xff1a; get /example更新节点的数据&#xff1a; set /example "Hello, Updated ZooKeeper!"删除节点&#xff1a; delete …