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…

纯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;如果一个词语…

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

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

四十一、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.问题现象描述 …

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 …

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

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

cdh中的zookeeper怎么配置zoo.cfg

你手动改了zoo.cfg目录是不会生效的&#xff0c;因为是cdh在管控&#xff0c;所以只能通过cdh修改。 首先打开cdh。 xxx:7180 点击zookeeper 选配置&#xff0c;然后选高级 在右边找&#xff0c;有一个就是zoo.cfg&#xff0c;可以点击右边的感叹号。然后在里面编辑的就会直…

LabVIEW RT环境中因字符串拼接导致的系统崩溃问题

在LabVIEW实时操作系统&#xff08;RT&#xff09;环境中运行的应用程序出现字符串拼接后死机的问题&#xff0c;通常涉及内存管理、内存泄漏或其他资源管理问题。以下是一些指导和步骤&#xff0c;帮助解决这个问题&#xff1a; 1. 内存泄漏检测 字符串拼接会在内存中创建新…

Android14音频进阶之CarAudioManager::getOutputDeviceForUsage流程分析(七十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

2024 年 19 种最佳大型语言模型

大型语言模型是 2023 年生成式人工智能热潮背后的推动力。然而&#xff0c;它们已经存在了一段时间了。 LLM是黑盒 AI 系统&#xff0c;它使用深度学习对超大数据集进行处理&#xff0c;以理解和生成新文本。现代 LLM 开始成型于 2014 年&#xff0c;当时一篇题为“通过联合学…

Github2024-06-12 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4JavaScript项目2Lua项目1PHP项目1Blade项目1非开发语言项目1TypeScript项目1Shell项目1从零开始构建你喜爱的技术 创建周期:2156 天…

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

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

[imx6ull]Linux下的SocketCAN通信

文章目录 一、CAN总线协议1.简介2.电气属性3.通信原理①数据帧的帧格式&#xff1a;②总线同步③总线竞争④数据保护 二、Linux下CAN的操作1.硬件连接①CAN电平转换器②扩展板使用CAN 2.查询 can 信息3.开启/关闭 can4.发送/接收 can 数据5.设置 can 参数 三、CAN的回环测试四、…

【知识整理】软件版本号的定义及规范

版本号简述 在软件开发项目中&#xff0c;版本号是一个非常重要的概念&#xff0c;它能够告诉用户软件的功能、质量和安全性等信息&#xff0c;同时也可以帮助开发者追踪软件的历史和进展&#xff0c;并做好版本控制工作。在本文中&#xff0c;我们将介绍版本号的定义及规范&a…

Java基础面试重点-3

41. 简述线程生命周期(状态) 其它参考《多线程重点》中的说法。三种阻塞&#xff1a; 等待阻塞&#xff1a; 运行的线程执行o.wait()方法&#xff08;该线程已经持有锁&#xff09;&#xff0c;JVM会把该线程放入等待队列中。同步阻塞&#xff1a; 运行的线程在获取对象的同步…

数据挖掘丨轻松应用RapidMiner机器学习内置数据分析案例模板详解(下篇)

RapidMiner 案例模板 RapidMiner 机器学习平台提供了一个可视化的操作界面&#xff0c;允许用户通过拖放的方式构建数据分析流程。RapidMiner目前内置了 13 种案例模板&#xff0c;这些模板是预定义的数据分析流程&#xff0c;可以帮助用户快速启动和执行常见的数据分析任务。 …