C++模板——非类型模板参数、模板的特化以及模板的分离编译

news2024/11/28 4:29:24

目录

非类型模板参数

模板的特化

概念

函数模板特化

类模板特化

全特化

偏特化

模板的分离编译

什么是分离编译

模板的分离编译

解决方法

模板总结


非类型模板参数

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

例如,我们要实现一个静态数组的类,就需要用到非类型模板参数。

#include <iostream>

template <class T, std::size_t N>
class StaticArray {
public:
    // 获取数组大小
    constexpr std::size_t arraysize() const {
        return N;
    }

    // 获取数组中的元素(带边界检查)
    T& operator[](std::size_t index) {
        if (index >= N) {
            throw std::out_of_range("Index out of range");
        }
        return _array[index];
    }

    // 获取数组中的元素(常量版本,带边界检查)
    const T& operator[](std::size_t index) const {
        if (index >= N) {
            throw std::out_of_range("Index out of range");
        }
        return _array[index];
    }

    // 填充数组中的所有元素
    void fill(const T& value) {
        for (std::size_t i = 0; i < N; ++i) {
            _array[i] = value;
        }
    }

    // 打印数组内容
    void print() const {
        for (std::size_t i = 0; i < N; ++i) {
            std::cout << _array[i] << " ";
        }
        std::cout << std::endl;
    }

private:
    T _array[N]; // 利用非类型模板参数指定静态数组的大小
};

int main() {
    StaticArray<int, 5> arr;

    // 填充数组
    arr.fill(10);

    // 打印数组内容
    arr.print();

    // 访问和修改数组元素
    arr[2] = 20;
    arr.print();

    // 获取数组大小
    std::cout << "Array size: " << arr.arraysize() << std::endl;

    // 尝试访问越界元素
    try {
        std::cout << arr[5] << std::endl; // 这将抛出异常
    } catch (const std::out_of_range& e) {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}
  1. 模板声明

    • template <class T, std::size_t N>:这个类模板接受两个参数:一个类型参数T和一个非类型模板参数N,表示数组的大小。
  2. 成员函数

    • constexpr std::size_t arraysize() const:返回数组的大小,使用constexpr保证在编译时常量。
    • T& operator[](std::size_t index)const T& operator[](std::size_t index) const:重载的索引运算符用于访问数组元素,包含边界检查以防止越界访问。
    • void fill(const T& value):填充数组的所有元素为指定的值。
    • void print() const:打印数组内容。
  3. 数据成员

    • T _array[N]:声明一个静态数组作为类的成员,大小为模板参数N
  4. main函数

    • 创建一个StaticArray<int, 5>对象。
    • 使用fill方法填充数组,打印数组内容。
    • 修改数组元素并打印内容。
    • 获取并打印数组大小。
    • 尝试访问越界元素,捕获并处理异常。

运行结果

 

这样,静态数组类不仅能够存储和管理固定大小的数组,还能提供便捷的方法进行访问和修改,同时具有边界检查功能来提高安全性。

模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板。
// 函数模板 -- 参数匹配
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指 针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化类模板特化。

函数模板特化

函数模板的特化步骤:
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;
}

注意: 一般情况下,如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。例如,上述实例char*类型的特化还可以这样给出:

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

类模板特化

类模板特化允许为特定的类型提供自定义的实现。特化可以分为完全特化和偏特化。 

全特化

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

#include <iostream>
using namespace std;

// 通用模板
template <typename T> // 声明一个类模板
class Printer {
public:
    void print(const T& value) { // 声明一个名为 print 的公共成员函数,参数是一个常量引用类型 T
        cout << "Generic Printer: " << value << endl; 
    }
};

// 完全特化版本,用于 const char*
template <> // 声明一个完全特化版本,不需要类型参数
class Printer<const char*> { // 特化 Printer 类模板用于 const char* 类型
public:
    void print(const char* value) { // 声明一个名为 print 的公共成员函数,参数是一个 const char* 类型
        cout << "Specialized Printer for const char*: " << value << endl;
    }
};

int main() {
    Printer<int> intPrinter; // 声明一个 Printer<int> 类型的对象 intPrinter,使用通用模板
    intPrinter.print(123);  // 

    Printer<const char*> stringPrinter; // 声明一个 Printer<const char*> 类型的对象 stringPrinter,使用完全特化版本
    stringPrinter.print("Hello, world!");  // 调用 stringPrinter 对象的 print 成员函数,输出 "Specialized Printer for const char*: Hello, world!"

    return 0; 
}

全特化的特点

  1. 全特化的声明
    • template <>: 声明一个完全特化版本。不需要类型参数,因为这是为特定类型(const char*)提供的实现。
  2. 全特化的定义
    • class Printer<const char*>: 为 Printer 类模板定义一个专门用于 const char* 类型的特化版本。
  3. 全特化的用途
    • 对于特定类型(如 const char*),我们可以提供不同于通用模板的实现,以满足特定需求或优化性能。
    • 在这个例子中,完全特化版本的 print 函数处理 const char* 类型的字符串,并输出特定的信息。

通用模板和全特化的对比

  • 通用模板:可以处理任何类型 T,提供了一个通用的实现。适用于大多数情况。
  • 完全特化:针对特定类型 const char* 提供了一个专门的实现。这种实现可以与通用模板不同,用于满足特定的需求或优化。

偏特化

偏特化是指任何针对模板参数进一步进行条件限制设计的特化版本。 

#include <iostream>
using namespace std;

// 通用模板
template <typename T, typename U>
class Pair {
public:
    void print() {
        cout << "Generic Pair" << endl;
    }
};

// 偏特化版本,当两个参数类型相同时
template <typename T>
class Pair<T, T> {
public:
    void print() {
        cout << "Specialized Pair with same types" << endl;
    }
};

int main() {
    Pair<int, double> p1; // 声明一个 Pair<int, double> 类型的对象 p1,使用通用模板
    p1.print();  // 调用 p1 对象的 print 成员函数,输出 "Generic Pair"

    Pair<int, int> p2; // 声明一个 Pair<int, int> 类型的对象 p2,使用偏特化版本
    p2.print();  // 调用 p2 对象的 print 成员函数,输出 "Specialized Pair with same types"

    return 0; 
}
  1. 偏特化的声明

    • 在模板参数列表中使用了相同的模板参数 T,表示只有当两个参数类型相同时才会触发偏特化。
  2. 偏特化的定义

    • template <typename T>: 声明一个类模板,T 是一个模板参数,占位符类型。
    • class Pair<T, T>: 声明一个偏特化版本,当两个模板参数的类型相同时触发。
  3. 偏特化的用途

    • 当模板参数满足特定条件时,我们可以提供一个不同于通用模板的特化实现。
    • 在这个示例中,偏特化版本的 print 函数处理两个参数类型相同的情况,并输出特定的信息。

通用模板和偏特化的对比

  • 通用模板:提供了一个通用的实现,适用于大多数情况。
  • 偏特化:针对特定模板参数满足特定条件时,提供了一个特定的实现。这种实现可以与通用模板不同,用于满足特定的需求或优化。

偏特化可以在满足特定条件的情况下提供更特定的实现,从而使模板更加灵活和适用于各种不同的情况。

模板的分离编译

什么是分离编译

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

模板的分离编译

在分离编译模式下,我们一般创建三个文件,一个头文件用于进行函数声明,一个源文件用于对头文件中声明的函数进行定义,最后一个源文件用于调用头文件当中的函数。
按照此方法,我们若是对一个加法函数模板进行分离编译,其三个文件当中的内容大致如下:

但是使用这三个文件生成可执行文件时,却会在链接阶段产生报错。

下面我们对其进行分析:
我们都知道,程序要运行起来一般要经历以下四个步骤:

预处理: 头文件展开、去注释、宏替换、条件编译等。
编译: 检查代码的规范性、是否有语法错误等,确定代码实际要做的工作,在检查无误后,将代码翻译成汇编语言
汇编:
把编译阶段生成的文件转成目标文件。
链接: 将生成的各个目标文件进行链接,生成可执行文件。
以上代码在预处理阶段需要进行头文件的包含以及去注释操作。

这三个文件经过预处理后实际上就只有两个文件了,若是对应到Linux操作系统当中,此时就生成了 Add.i 和 main.i 文件了。

 

预处理后就需要进行编译,虽然在 main.i 当中有调用Add函数的代码,但是在 main.i 里面也有Add函数模板的声明,因此在编译阶段并不会发现任何语法错误,之后便顺利将 Add.i 和 main.i 翻译成了汇编语言,对应到Linux操作系统当中就生成了 Add.s 和 main.s 文件。

之后就到达了汇编阶段,此阶段利用 Add.s 和 main.s 这两个文件分别生成了两个目标文件,对应到Linux操作系统当中就是生成了 Add.o 和 main.o 两个目标文件。

前面的预处理、编译和汇编都没有问题,现在就需要将生成的两个目标文件进行链接操作了,但在链接时发现,在main函数当中调用的两个Add函数实际上并没有被真正定义,主要原因是函数模板并没有生成对应的函数,因为在全过程中都没有实例化过函数模板的模板参数T,所以函数模板根本就不知道该实例化T为何类型的函数。

模板分离编译失败的原因:
在函数模板定义的地方(Add.cpp)没有进行实例化,而在需要实例化函数的地方(main.cpp)没有模板函数的定义,无法进行实例化。

解决方法

解决类似于上述模板分离编译失败的方法有两个,第一个就是在模板定义的位置进行显示实例化。
例如,对于上述代码解决方案如下:

在函数模板定义的地方,对T为int和double类型的函数进行了显示实例化,这样在链接时就不会找不到对应函数的定义了,也就能正确执行代码了。

虽然第一种方法能够解决模板分离编译失败的问题,但是我们这里并不推荐这种方法,因为我们需要用到一个函数模板实例化的函数,就需要自己手动显示实例化一个函数,非常麻烦。

现在就来说说解决该问题的第二个方法,也是我们所推荐的,那就是对于模板来说最好不要进行分离编译,不论是函数模板还是类模板,将模板的声明和定义都放到一个文件当中就行了。

模板总结

优点:

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

缺陷:

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

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

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

相关文章

垂类短视频:四川鑫悦里文化传媒有限公司

垂类短视频&#xff1a;内容细分下的新媒体力量 随着移动互联网的迅猛发展和智能手机的普及&#xff0c;短视频已成为当下最受欢迎的媒介形式之一。四川鑫悦里文化传媒有限公司而在短视频领域&#xff0c;一个新兴的概念——“垂类短视频”正逐渐崭露头角&#xff0c;以其独特…

【教程】利用API接口添加本站同款【每日新闻早早报】-每天自动更新,不占用文章数量

本次分享的是给网站添加一个每日早报的文章&#xff0c;可以看到本站置顶上面还有一个日更的日报&#xff0c;这是利用ALAPI的接口完成的&#xff01;利用接口有利也有弊&#xff0c;因为每次用户访问网站的时候就会增加一次API接口请求&#xff0c;导致文章的请求会因为请求量…

nodejs中使用ffmpeg零基础教程(electron+vue3)

同学们可以私信我加入学习群&#xff01; 正文开始 前言一、多方案对比二、ffmpeg各插件简介三、使用ffmpeg-static插件四、使用fluent-ffmpeg插件五、如果使用ai&#xff0c;可能会踩的坑5.1第一个坑5.2第二个坑5.3第三个坑 总结 前言 最近想要把自己写的一些知识点&#xff…

二十九、openlayers官网示例DeclutterGroup解析——避免矢量图层的文字重叠

官网demo地址&#xff1a; Declutter Group 这篇说的是如何设置矢量图层上多数据点文字不重叠。 主要是属性declutter &#xff0c;用于处理矢量图层上重叠的标注和符号&#xff0c;为true时启用去重叠功能。所有矢量特征的标注和符号都会被处理以避免重叠。false则与之相反。…

java nio FileChannel堆内堆外数据读写全流程分析及使用(附详细流程图)

这里是小奏,觉得文章不错可以关注公众号小奏技术 背景 java nio中文件读写不管是普通文件读写&#xff0c;还是基于mmap实现零拷贝&#xff0c;都离不开FileChannel这个类。 随便打开RocketMQ 源码搜索FileChannel 就可以看到使用频率 kafka也是 所以在java中文件读写FileCh…

hexo静态博客 部署到xxx.github.io github 静态页

hexo安装 npm install hexo-cli -g hexo init blog cd blog npm install hexo server key配置 ssh-keygen -t ed25519 -C “emaile.com” 添加key到github err gitgithub.com: Permission denied (publickey). fatal: Could not read from remote repository. 配置GitHub仓…

LabVIEW通过以太网控制PLC程序开发

在使用LabVIEW通过以太网控制PLC程序开发时&#xff0c;需要综合考虑硬件、软件和通信协议的协调工作。以下是详细步骤、注意事项、重点和难点分析&#xff0c;以及几种实现方式及其特点的概述。 实现步骤 确定硬件和软件环境&#xff1a; 确定PLC型号和品牌&#xff08;如西门…

民国漫画杂志《时代漫画》第29期.PDF

时代漫画29.PDF: https://url03.ctfile.com/f/1779803-1248635405-bf3c87?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

互联网的利

在互联网没发明之前&#xff0c;人类说话要近距离的说&#xff0c;玩游戏要近距离的玩&#xff0c;十分麻烦。于是&#xff0c;互联网解决了这个问题。聊天可以在电脑上聊&#xff0c;玩游戏可以用游戏软件查找玩家来玩&#xff0c;实现了时时可聊&#xff0c;时时可玩的生活。…

Euler 欧拉系统介绍

Euler 欧拉系统介绍 1 简介重要节点与版本EulerOS 特色EulerOS 与 openEuler 区别联系Euler 与 HarmonyOS 区别联系 2 openEuler特色支持 ARM&#xff0c;x86&#xff0c;RISC-V 等全部主流通用计算架构融入 AI 生态嵌入式实时能力提升引入 OpenHarmony 一些突出功能 参考 1 简…

基于51单片机简易温度计

一.硬件方案 本系统利用51单片机控制温度传感器DS18B20进行温度的实时检测并显示&#xff0c;能够实现快速测量环境温度。硬件以微控制器为核心&#xff0c;外接时钟电路、复位电路、温度测量电路、LED显示电路组成。 二.设计功能 &#xff08;1&#xff09;采用DS18B20温度…

python使用多种方法计算列表元素平方的技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、使用列表推导式进行元素平方 二、使用map函数进行元素平方 三、循环遍历列表进行元素平…

HTML 页面布局

慢慢生活&#xff0c;慢慢变好 —— 24.5.28 页面布局 盒子: 页面中所有的元素(标签)&#xff0c;都可以看做是一个盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成: 内容区域(content)、内边距区域(pa…

大模型时代的具身智能系列专题(四)

google deepmind团队 谷歌旗下最大的两个 AI 研究机构——地处伦敦 DeepMind 与位于硅谷的 Google Brain 合并成立新部门 Google DeepMind。其将机器学习和系统神经科学的最先进技术结合起来&#xff0c;建立强大的通用学习算法。代表作有AlphaGo&#xff0c;AlphaStar&#x…

Vanna使用ollama分析本地MySQL数据库

上一章节中已经实现了vanna的本地运行&#xff0c;但是大模型和数据库都还是远程的&#xff0c;因为也就没办法去训练&#xff0c;这节一起来实现vanna分析本地mysql数据库&#xff0c;因为要使用本地大模型&#xff0c;所以开始之前需要给本地安装好大模型&#xff0c;我这里用…

Android性能优化方案

1.启动优化&#xff1a; application中不要做大量耗时操作,如果必须的话&#xff0c;建议异步做耗时操作2.布局优化&#xff1a;使用合理的控件选择&#xff0c;少嵌套。&#xff08;合理使用include,merge,viewStub等使用&#xff09;3.apk优化&#xff08;资源文件优化&#…

浅揭秘:Java方法调用过程中栈内存到底干了什么

在深入Java编程的世界时&#xff0c;理解其方法调用背后的内存管理机制是至关重要的。 Java作为一种面向对象的语言&#xff0c;其内存管理自动化程度高&#xff0c;但背后涉及的原理却错综复杂&#xff0c;尤其是方法调用过程中的栈帧、堆、方法区等概念。 本文将通过代码示…

【踩坑】编译opencv将python (for build) python2.7改为python3

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 出现问题 默认是2.7 解决方案 cmake时候添加&#xff1a; -D PYTHON_DEFAULT_EXECUTABLE$(which python3)

豪赌?远见?浙江东方的量子冒险

今年4月16日&#xff0c;量子通信概念异动&#xff0c;浙江东方&#xff08;600120&#xff09;拉升涨停。 量子和浙江东方&#xff0c;要把这两个词联系起来似乎并不太容易。 浙江东方&#xff0c;即浙江东方金融控股集团股份有限公司&#xff0c;系浙江省国资委下属浙江省国…

CCF- CSP 2018.12 - 1.2题 Java语言解题

2018.12-1 小明上学 import java.util.Scanner;public class text01_RedLight {public static void main(String[] args) {Scanner scanner new Scanner(System.in);int r scanner.nextInt();int y scanner.nextInt();int g scanner.nextInt();int n scanner.nextInt();in…