• 【C++】模板特化、模板分离编译


    一、非类型模板参数

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

    当我们想定义以一个模板数组,但是不确定其长度时,我们只能以这种方式进行定义:
    模板参数

    此时便暴露出其问题,关于数组的长度设定,我们没有办法设置其符合泛型编程的特点。

    所以我们引入非类型模板参数的写法,就可以解决该问题。如下:
    非类型模板参数
    两者的区别:
    在这里插入图片描述

    这样,关于该成员变量数组的长度就可以由用户主动设置长度了。

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

    1.1 STL中的 array

    以上我们的举例,其实在 STL 中便有其原型 : array的介绍,其实原理就是按照我们上面的非类型模板参数来实现的。
    STL中的array
    其本质就是一个容器数组。
    使用区别:
    在这里插入图片描述
    可能大家感觉这定义这么相似,两者间没有太大任何区别。

    array本质上是一个容器,是使用对象定义的。
    而 数组本质就是指针

    既然 C++11 带来了 array ,就说明其必然是有优势所在。
    优势在于:

    array中,不管越界写、越界读都会被检查到。其对越界的检查非常严格
    本质上,array 对越界的检查是通过函数的调用来检查,而数组本质上是指针解引用,报错、奔溃与否与编译器有很大关系。编译器检查越界是采用抽查的方式,而且只针对越界写入,越界读不检查。

    二、模板的特化

    2.1 概念

    通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数。
    之前我们实现了一个日期类,那现在出现了以下场景:
    在这里插入图片描述
    大家发现,为什么第三个结果是错误的呢?
    因为我们传入的是指针,所以 Greater 函数中是按两个指针的地址来比较的。
    这肯定不是我们想要的结果。我们想即使我们传入日期对象的地址,调用该函数,内部使用指针指向的对象进行比较。
    所以,这里便引入模板特化的技术。
    **特化———针对某些类型进行特殊化处理**

    2.2 函数模板特化

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

    接下来我们将 Greater 进行特化(注意写法!),然后我们来观察结果。
    在这里插入图片描述

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

    bool Greater(Date* left,Date* right)
    {
    	return *left< *right ;
    } 
    
    • 1
    • 2
    • 3
    • 4

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

    2.3 类模板特化

    我们使用仿函数代替上面的 Greater 函数,同样会出现以下这样情况:
    在这里插入图片描述

    所以,接下来我们对上面的仿函数进行特化,大家注意特化的格式。

    在这里插入图片描述
    此时可能会有人觉得特化很鸡肋,这里举一个很常见的使用场景。
    比如之前实现的 priority_queue (优先级队列),实现了特化,我们可以直接将指针传入存放到优先级队列中,这样,队列中即是有序数据,而且提高了入队列、调整节点的效率。
    在这里插入图片描述

    2.3.1 全特化

    其实特化分为全特化和偏(半)特化,具体是什么样的,以及其使用场景,我们一起往下学习。

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

    全特化定义方式:
    在这里插入图片描述

    2.3.2 偏特化

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

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

    • 部分特化

    一个参数可以是任意类型,而第二个参数类型必须为 char 型。

    在这里插入图片描述

    • 参数更进一步的限制

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

    在这里插入图片描述

    接下来是引用偏特化,这个使用就比较奇特了。

    在这里插入图片描述

    当然,多变的偏特化还可以同时使用引用和指针

    在这里插入图片描述
    有了以上的基础,所以我们可以对我们上面的特化进行升级,直接使用偏特化,只要是指针,就调用该特化在这里插入图片描述
    这样,可以进一步增强代码的通用性。

    三、模板分离编译

    3.1 什么是分离编译

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

    3.2 模板的分离编译

    模板不支持分离编译

    一般的 cpp 文件,test.h 中放入声明,test.cpp 放定义。

    例如现在,我们将我们自己实现的 vector 容器中的 push_back 和 insert 放到另外一个文件中进行定义。

    第一步就会碰到以下错误:

    在这里插入图片描述

    为什么这个地方要加上 typename

    在这里插入图片描述

    原因:
    因为面对这种场景,编译器无法区分用户这样书写,表示的是一个变量还是一个类型,所以需要我们人为去指定该场景下到底是变量还是类型,所以这里我们加上 typename 声明此时是类型。

    接着我们运行我们的main()函数。

    此时便出现了链接的错误。
    而链接的错误提示为有声明push_back的声明,但找不到其定义。
    在这里插入图片描述
    为什么找不到 push_back 呢?

    因为 push_back 没有被实例化.

    在这里插入图片描述

    总结:
    1.其余函数在编译期间就已实例化,确定了地址。
    2.而push_back在vector.h外,在链接阶段进行匹配
    3.push_back 由模板定义,由于模板参数还未实例化,则无法被编译为指令
    4.所以push_back未进入符号表,无法被链接

    解决方法:

    1.模板的声明和定义不分离,或只分离到同一个文件中。
    2.显示实例化,直接指定该模板参数的类型,然其进入符号表即可。

    在这里插入图片描述
    但是这种写法非常的鸡肋,如果此时 模板参数不为 int ,那这里又要添加一次显示实例化。
    在这里插入图片描述
    再添加一条显示实例化:
    在这里插入图片描述
    所以,这种方法并不推荐。
    这里有一篇关于模板分离式编译更详细的讲解:
    为什么C++编译器不能支持对模板的分离式编译

    四、模板总结

    优点

    • 增强复用了代码,节省资源,更快的迭代开发,将重复的工作交给了编译器
    • C++的标准模板库(STL)因此而生
    • 增强了代码的灵活性

    缺陷

    • 模板会导致代码膨胀问题,也会导致编译时间变长
    • 出现模板编译错误时,错误信息非常凌乱,不易定位错误
  • 相关阅读:
    Android实战——一步一步实现流动的炫彩边框
    顺序表的实现(增删查改)
    keep-alive 是 Vue 的一个内置组件,用于缓存其他组件的实例,以避免重复渲染和销毁,它可以在需要频繁切换的组件之间提供性能优化
    Java全栈开发第一阶段--01.Java基础编程(基本语法-进制)
    【Java从入门到精通 03】:Java运算符及进制转换
    商务社交中为何电子名片这么火?都在使用哪一款免费的电子名片?
    【SQL性能优化】Hash索引的底层原理是什么?
    【openGauss-3.0.0单节点安装】
    C++函数对象包装器function类详解
    Spring中value的#和$区别
  • 原文地址:https://blog.csdn.net/Brant_zero/article/details/127414938