C++第二十八弹---进一步理解模板:特化和分离编译

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】

目录

1. 非类型模板参数

2. 模板的特化

2.1 概念

2.2 函数模板特化

2.3 类模板特化

2.3.1 全特化

2.3.2 偏特化

2.3.3 类模板特化应用示例

3. 模板分离编译

3.1 什么是分离编译

3.2 模板的分离编译

3.3 解决方法

4. 模板总结


1. 非类型模板参数


模板参数类 类型形参非类型形参


类 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

由STL库中静态顺序表array举例,我们声明类大小时,需要固定元素个数,在C语言中我们通常使用宏来定义常量,如下:

#define N 10  namespace lin
{template<class T>class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}

上面的代码中有一个缺陷,如果我们想实例化元素个数为10和元素个数为100的静态顺序表,我们的办法就是实例化两个元素个数为100的静态顺序表,但是此时会浪费很大的空间。由于上述原因我们就可以引出非类型模板参数,代码实现如下: 

namespace lin
{// 只支持整型做非类型模板参数,浮点数,类对象,自定义类型不能// 类型模板参数    class 对象// 非类型模板参数   类型 常量template<class T , size_t N = 10>// 使用缺省参数class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}

 使用上述的代码声明类时,可以根据自己的需求实例化不同大小的静态顺序表类。使用如下:

int main()
{// 实例化大小不同的类lin::array<int> a1; // 默认 N = 10lin::array<int, 1000> a2; // N = 1000return 0;
}

注意:

1. 浮点数、类对象以及字符串不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。

2. 模板的特化


2.1 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

日期类

class Date
{
public:friend ostream& operator<<(ostream& _cout, const Date& d);Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}
private:int _year;int _month;int _day;
};ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}

代码演示 

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl;// 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。


此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化


2.2 函数模板特化


函数模板的特化步骤:

1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了return 0;
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

bool Less(Date* left, Date* right)
{return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。


2.3 类模板特化


2.3.1 全特化


全特化即是将模板参数列表中所有的参数都确定化

template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};
template<>
class Data<int, char>//两个模板参数均特别处理
{
public:Data() { cout << "Data<int, char>" << endl; }
};
void Test()
{Data<int, int> d1;//调用模板Data<int, char> d2;//调用全特化
}

2.3.2 偏特化


偏特化(半特化):任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:

template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};

偏特化有以下两种表现方式:


1.部分特化将模板参数类表中的一部分参数特化。

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
};

2.参数更进一步的限制。


偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。

// 两个参数偏特化为指针类型
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
};// 一个参数特化为指针类型,一个参数特化为引用类型
template<class T1,class T2>
class Data<T1&, T2*>
{
public:Data() { cout << "Data<T1&, T2*>" << endl; }
};int main()
{Data<int*, int*> d4;// 调用特化的指针版本Data<int&, int*> d5;// 调用一个为指针,一个为引用类型return 0;
}

2.3.3 类模板特化应用示例


有如下专门用来按照小于比较的类模板Less:

#include<vector>
#include <algorithm>
template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vector<Date> v1;v1.push_back(d1);v1.push_back(d2);v1.push_back(d3);// 可以直接排序,结果是日期升序sort(v1.begin(), v1.end(), Less<Date>());vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期sort(v2.begin(), v2.end(), Less<Date*>());vector<Date*>::iterator it = v2.begin();while (it != v2.end()){cout << *(*it) << " ";//打印日期++it;}return 0;
}

 

通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题:

// 对Less类模板按照指针方式特化
template<class T>
struct Less<T*>
{bool operator()(const T* x, const T* y) const{return *x < *y;}
};

特化之后,在运行上述代码,就可以得到正确的结果。
 

3. 模板分离编译


3.1 什么是分离编译


一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.2 模板的分离编译


假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

Array.h

// 声明
namespace lin
{template<class T, size_t N = 10>class array{public:size_t size()const;private:T _array[N];size_t _size = 0;// 缺省值初始化};void func();
}

Array.cpp

// 定义
namespace lin
{template<class T,size_t N>size_t array<T, N>::size()const{return _size;}void func(){cout << "void func()" << endl;}
}

Test.cpp 

int main()
{lin::array<int> a1;// 构造函数cout << a1.size() << endl;lin::func();
}

 在上述代码中,当调用size函数时会报链接错误,而调用func函数则不会报错,为什么呢???

// size与func都只有声明,编译时,检查一下函数名和参数匹配,没问题则暂且通过
// 定义在.cpp文件,链接的时候再去其他文件中找函数地址

// 调用的地方,知道实例化T成什么类型,但是只有声明没有定义 

// 定义的地方,不知道实例化T成什么类型,所以有定义无法实例化,也就是无法生成函数的地址到符号表

3.3 解决方法


1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。

在.h文件中声明+定义
// .h预处理展开后,实例化模板时,既有声明,又有定义,直接就实例化
// 编译时,有函数的定义,直接就有地址,不需要链接去找

如下:

// 声明定义在同一个.h文件
namespace lin
{template<class T, size_t N = 10>class array{public:size_t size()const;private:T _array[N];size_t _size = 0;// 缺省值初始化};template<class T, size_t N>size_t array<T, N>::size()const{return _size;}void func();
}


2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

namespace lin
{template<class T, size_t N = 10>class array{public:size_t size()const;private:T _array[N];size_t _size = 0;// 缺省值初始化};// 显示实例化templateclass array<int>;templateclass array<double>;
}

显示实例化的缺陷是一个类型就需要实例化一次,比较麻烦。

4. 模板总结


【优点】

1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
2. 增强了代码的灵活性。


【缺陷】

1. 模板会导致代码膨胀问题,也会导致编译时间变长。
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误 。

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

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

相关文章

前端学习7——自学习梳理

​​​​​​jQuery 教程 | 菜鸟教程jQuery 教程 jQuery 是一个 JavaScript 库。 jQuery 极大地简化了 JavaScript 编程。 jQuery 很容易学习。 本章节的每一篇都包含了在线实例 通过本站的在线编辑器&#xff0c;你可以在线运行修改后的代码&#xff0c;并查看运行结果。 实例…

Redis的事务_乐观锁与悲观锁

目录 一 Redis事务-介绍 二 事务的基本操作 三 Redis事务-乐观锁与悲观锁 四 Redis事务-特性 一 Redis事务-介绍 Redis事务可以一次执行多个命令&#xff0c;本质是一组命令的集合&#xff0c;一个事务中的所有命令都会序列化&#xff0c;按顺序的串行化执行&#xff0c;而…

【开源库学习】libodb库学习(十二)

13 数据库架构演变 当我们添加新的持久类或更改现有的持久类时&#xff0c;例如&#xff0c;通过添加或删除数据成员&#xff0c;存储新对象模型所需的数据库模式也会发生变化。同时&#xff0c;我们可能有包含现有数据的现有数据库。如果应用程序的新版本不需要处理旧数据库&a…

使用 XRDP 远程linux主机

一、简介 XRDP是一个开源的远程桌面协议&#xff08;Remote Desktop Protocol,RDP&#xff09;服务器&#xff0c;采用的是标准的RDP。 官网地址&#xff1a;https://www.xrdp.org/ github地址&#xff1a; https://github.com/neutrinolabs/xrdp/releases XRDP也是C/S架构&…

右值引用与移动构造详解

右值引用与移动构造 这节我们来详细的介绍一下什么是左值引用&#xff0c;什么是右值引用&#xff0c;以及为什么要引入右值引用&#xff0c;还有就是c11非常重要的特性 -> 移动构造 左值引用和右值引用 ​ 左值是一个表示数据的表达式(如变量名或解引用的指针)&#xff0…

Springboot 整合Elasticsearch

1 java操作ES方式 1.1 操作ES 9300端口(TCP) 但开发中不在9300进行操作 ES集群节点通信使用的也是9300端口如果通过9300操作ES&#xff0c;需要与ES建立长连接 可通过引入spring-data-elasticsearch:transport-api.jar不在9300操作原因&#xff1a;1.springboot版本不同&…

Spring Core——资源加载与访问(Resource)

Spring 中的资源加载 在Spring框架中&#xff0c;Resource接口用于简化和统一对各种底层资源&#xff08;如xxx.xml、application.yml、application.properties等文件、类路径资源、URL等&#xff09;的访问。它提供了一个通用的抽象层&#xff0c;使开发者无需关注不同资源类…

Cloud Native 安全实践解析

Cloud Native 安全实践解析 一、Cloud Native概述 Cloud Native&#xff08;云原生&#xff09;是一种构建和运行应用程序的方法&#xff0c;它充分利用了云计算的弹性、可扩展性和多租户特性。云原生应用通常被设计成微服务架构&#xff0c;利用容器化技术进行部署和管理&am…

springboot电影院线上购票系统-计算机毕业设计源码68220

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统流程分析 2.2.1 添加信息流程 2.2.2 修改信息流程 2.2.3 删除信息流程 2.3 系统功能分析 2.…

Fireflyrk3288 ubuntu18.04添加Qt开发环境、安装mysql-server

1、创建一台同版本的ubuntu18.04的虚拟机 2、下载rk3288_ubuntu_18.04_armhf_ext4_v2.04_20201125-1538_DESKTOP.img 3、创建空img镜像容器 dd if/dev/zero ofubuntu_rootfs.img bs1M count102404、将该容器格式化成ext4文件系统 mkfs.ext4 ubuntu_rootfs.img5、将该镜像文件…

起薪4万的AI产品经理自述:一个算法模型是怎么训练出来的?

起薪4万的AI产品经理自述&#xff1a;一个算法模型是怎么训练出来的&#xff1f; 这篇文章&#xff0c;我们继续来讲模型构建的其他 3 个环节&#xff1a;模型训练、模型验证和模型融合。 模型训练 模型训练是通过不断训练、验证和调优&#xff0c;让模型达到最优的一个过程。…

切割01串问题(dp动态规划问题)

题目概述&#xff1a; 给定一个长度为 &#x1d45b; 的 01 串&#xff0c;定义如下操作为一次 “切割”&#xff1a; 将长度大于 1 的字符串分割为两个非空的连续字串&#xff0c;记分割出来的左侧字串 a 中 0 的出现次数为 C 0 C_0 C0​&#xff0c;右侧字串 b 中 1 出现的…

ChatGPT:Java 的文档标准 OAS 是什么的缩写

ChatGPT&#xff1a;Java 的文档标准 OAS 是什么的缩写 OAS 是 “OpenAPI Specification” 的缩写。OpenAPI Specification 是一个用于描述和定义 RESTful APIs 的标准。最初由 Swagger 开发&#xff0c;现在由 OpenAPI Initiative 维护。OAS 使用一种标准的格式&#xff08;通…

【人工智能】穿越科技迷雾:解锁人工智能、机器学习与深度学习的奥秘之旅

文章目录 前言一、人工智能1. 人工智能概述a.人工智能、机器学习和深度学习b.人工智能发展必备三要素c.小案例 2.人工智能发展历程a.人工智能的起源b.发展历程 3.人工智能的主要分支 二、机器学习1.机器学习工作流程a.什么是机器学习b.机器学习工作流程c.特征工程 2.机器学习算…

基于GEC6818开发板+Linux+Qt设计的智能养老院出入管理系统

一、前言 1.1 项目介绍 【1】项目功能介绍 随着我国老龄化进程的加快,养老问题日益突出,如何有效保障老年人的生活质量与安全成为社会关注的重点。智能化、信息化技术的发展为解决这一问题提供了新的思路和手段。基于Linux系统的智能养老院出入管理系统应运而生,为了实现…

Thinkphp仿华为商城源码/红色风格电脑手机数码商城系统网站源码

Thinkphp仿华为商城&#xff0c;主要实现了商品首页展示、用户意见、商品分类列表、商品搜索、商品详细展示、购物车、订单生成、在线付款、以及个人中心完善个人资料、用户修改收货地址、余额查询、消费查询、订单管理、商品评价、热销商品和最近商品浏览&#xff1b; 后台是…

大模型的架构演进史——为什么Decoder-Only成为最终的胜利者

文章目录 大模型的架构encoder onlydecoder nolyencoder-decoder为什么现在decoder-only为主流 大模型的架构 encoder only 使用encoder-only的模型主要的思路是通过编码器&#xff0c;将大量文本、时序数据等资料进行编码、压缩&#xff0c;达到进一步抽象理解输入数据的能力…

WEB前端12-axios基础

Vue2-axios基础 1.axios基本概念 在现代的前端开发中&#xff0c;处理网络请求是至关重要的一部分。Axios 是一个流行的基于 Promise 的 HTTP 客户端&#xff0c;它可以在浏览器和 Node.js 环境中使用。它的设计简单易用&#xff0c;支持并行请求、拦截器、CSRF 防护等特性&a…

【JavaScript】函数的动态传参

Javacript&#xff08;简称“JS”&#xff09;是一种具有函数优先的轻量级&#xff0c;解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名&#xff0c;但是它也被用到了很多非浏览器环境中&#xff0c;JavaScript基于原型编程、多范式的动态脚本语言&…

Linux 常用命令之文件处理

Linux 文件处理命令指南 文件查看命令 cat (Concatenate and display files) # 显示文件内容 cat file.txt# 显示多个文件的内容 cat file1.txt file2.txt# 将文件内容合并并输出到新文件 cat file1.txt file2.txt > combined.txt# 以行号显示文件内容 cat -n file.txtta…