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)本身是一个模板,使用它时用户还必须为那些在特例化版本中为指定的模板参数提供实参。

特例化成员而不是类

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Airtest 使用指南

Airtest 介绍 准备工作 AirtestIDE 安装与启动: https://airtest.doc.io.netease.com/IDEdocs/getting_started/AirtestIDE_install/ 电脑端的准备工作完成后,对于手机端只需要打开允许USB调试,当首次运行时会提示安装PocoService,同意即可。 界面介绍

【CT】LeetCode手撕—53. 最大子数组和

目录 题目1-思路2- 实现⭐53. 最大子数组和——题解思路 3- ACM 实现 题目 原题连接&#xff1a;53. 最大子数组和 1-思路 动规五部曲 1. 定义 dp 数组 dp[i] 含义为&#xff1a;下标为 i 的数组的最大子数组和 2. 递推公式 因为所求的是最大子数组的和&#xff0c;即当前 n…

群辉其它远程访问方案(Cpolar篇)

目录 1、下载NAS套件安装包 2、手动安装 3、配置 4、访问 &#xff08;1&#xff09;网页 &#xff08;2&#xff09;手机管家 &#xff08;3&#xff09;助手 &#xff08;4&#xff09;DS File 群辉的远程访问&#xff0c;最标准的做法就是使用群辉自己的DDNS&#x…

飞腾派初体验(2)

水个字数&#xff0c;混个推广分&#xff0c;另外几个点还是想吐槽一下 - 1&#xff0c;上篇文章居然没有给开发板一个硬照&#xff0c;补上 - 飞腾派 自拍 2. 现在做镜像用Win32DiskImager的多吗&#xff1f;我记得当年都是dd命令搞定&#xff0c;玩树莓派的应该记得这个命令…

OpenAPI Typescript Codegen 的基本使用

下载 axios npm install axios OpenAPI Typescript Codegen 官网&#xff1a;https://github.com/ferdikoomen/openapi-typescript-codegen 安装 OpenAPI Typescript Codegen npm install openapi-typescript-codegen --save-dev–input&#xff1a;指定接口文档的路径、url …

安装Pygame

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Pygame是跨平台的Python模块&#xff0c;专为电子游戏设计&#xff08;包含图像、声音&#xff09;&#xff0c;创建在SDL&#xff08;Simple Direct…

【HarmonyOS - UIAbility组件和UI的数据同步】

简述 基于HarmonyOS的应用模型&#xff0c;可以通过以下几种方式来实现UIAbility组件与UI之间的数据同步。 使用EventHub进行数据通信&#xff1a;基于发布订阅模式来实现&#xff0c;事件需要先订阅后发布&#xff0c;订阅者收到消息后进行处理。使用globalThis进行数据同步…

【Linux】进程控制2——进程等待(waitwaitpid)

1. 进程等待必要性 我们知道&#xff0c;子进程退出&#xff0c;父进程如果不管不顾&#xff0c;就可能造成"僵尸进程”的问题&#xff0c;进而造成内存泄漏。另外&#xff0c;进程一旦变成僵尸状态&#xff0c;那就刀枪不入&#xff0c;“杀人不眨眼”的kill -9 也无能为…

Git基础指令(图文详解)

目录 Git概述Git基础指令Linux系统操作指令 Git软件指令1.配置信息2.名称和邮箱3.初始化版本库4.向版本库中添加文件5.修改版本库文件6. 查看版本库文件历史 7.删除文件8.恢复历史文件 Git概述 Git基础指令 Linux系统操作指令 Git是一款免费、开源的分布式版本控制系统&…

MPLS工作过程

数据层面&#xff1a; 1) 没有 MPLS 协议&#xff0c;基于 FIB 表正常转发即可 2) 名词&#xff1a;MPLS domain——MPLS 的工作半径 edge LSR(PE)——边界标签交换路由器 工作 mpls 域的边缘&#xff0c;连接域外设备 …

【Redis】安装和命令行客户端

https://www.bilibili.com/video/BV1cr4y1671t https://www.oz6.cn/articles/58 redis 非结构化有&#xff1a; 键值类型(Redis)文档类型(MongoDB)列类型(HBase)Graph:类型(Neo4j) 扩展性&#xff1a;水平即为分布式扩展 redis特征 键值&#xff08;key-value&#xff09;型…

css实现优惠券样式

实现优惠券效果&#xff1a; 实现思路&#xff1a; 需要三个盒子元素&#xff0c;使用 css 剪裁&#xff0c;利用 ellipse 属性&#xff0c;将两个盒子分别裁剪成两个半圆&#xff0c;位置固定在另一个盒子元素左右两边适当位置上。为另一个盒子设置想要的样式&#xff0c;圆角…

杨氏矩阵和杨辉三角的空间复杂度较小的解题思路

文章目录 题目1 杨氏矩阵题目2 杨辉三角 题目1 杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 思路: 我们可以通过题目…

Python学习笔记7:入门知识(七)

前言 之前说过我更换了新的学习路线&#xff0c;现在是根据官方文档和书籍Python crash course来进行学习的&#xff0c;在目前的学习中&#xff0c;对于之前的知识有一些遗漏&#xff0c;这里进行补充。 学习资料有两个&#xff0c;书籍中文版PDF&#xff0c;关注我私信发送…

OpenCV学习(4.11) OpenCV中的图像转换

1. 目标 在本节中&#xff0c;我们将学习 使用OpenCV查找图像的傅立叶变换利用Numpy中可用的FFT功能傅立叶变换的一些应用我们将看到以下函数&#xff1a;**cv.dft()** &#xff0c;**cv.idft()** 等 理论 傅立叶变换用于分析各种滤波器的频率特性。对于图像&#xff0c;使用…

高并发挑战?盘点这些架构优化篇技巧,让你的系统焕发新生!

高并发挑战&#xff1f;试试这些垂直优化技巧&#xff0c;让你的系统焕发新生&#xff01; 背景介绍性能优化优化方向架构演进历程第一阶段&#xff1a;单体架构弊端瓶颈Tomcat与数据库独立部署瓶颈 第二阶段&#xff1a;缓存架构结合本地缓存和分布式缓存瓶颈 第三阶段&#x…

STM32-17-DAC

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU STM32-14-FSMC_LCD STM32-15-DMA…

【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数

效果见图&#xff1a; shader 代码修改如下&#xff0c; 主要看 USE_MASK_UVY 关键字部分修改&#xff1a; // Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd. CCEffect %{techniques:- name: opaquepasses:- vert: unlit-vs:vertfrag: unlit-fs:fragproperties: &a…

Python 踩坑记 -- 调优

前言 继续解决问题 慢 一个服务运行有点慢&#xff0c;当然 Python 本身不快&#xff0c;如果再编码不当那这个可能就是量级上的劣化。 整个 Code 主线逻辑 1700&#xff0c;各依赖封装 3000&#xff0c;主线逻辑也是很久远的痕迹&#xff0c;长函数都很难看清楚一个 if els…

【因果推断python】32_合成控制2

目录 合成控制作为线性回归的一种实现​编辑 合成控制作为线性回归的一种实现 为了估计综合控制的治疗效果&#xff0c;我们将尝试构建一个类似于干预期之前的治疗单元的“假单元”。然后&#xff0c;我们将看到这个“假单位”在干预后的表现。合成控制和它所模仿的单位之间的…