C++ 进阶之路:非类型模板参数、模板特化与分离编译详解

news2024/9/24 16:59:12

目录

非类型模版参数

类型模板参数

非类型模板参数

非类型模板参数的使用

模板的特化

函数模板的特化

类模板的特化

全特化与偏特化

偏特化的其它情况

模板的分离编译

什么是分离编译

为什么要分离编译

为什么模板不能分离编译

普通的类和函数都是可以分离编译的。

同样是分离编译,普通函数/类可以,函数模板、类模板为什么不行??? 

编译链接有哪些过程?

链接的时候到底做了什么??

解决模板不能分离编译的方法

显示实例化

不要分离编译


非类型模版参数

在 C++ 中,非类型模板参数是一种在模板定义中使用的参数类型,它不是一个数据类型,而是一个具体的值或对象引用。

类型模板参数

//类型模板参数
template<class T>
class A
{};

非类型模板参数

 那么什么是非类型模板参数呢?首先,我们来观察以下代码

假设我想要一个数组的大小一个是100,一个是1000,只能把这个 N 要么给 100 要么给1000,或者再定义一个类出来一个N 100一个 N 1000,如果这样改的话代码过,代码处理不同类型不够灵活,所以我们就有了非类型模板参数

#define N 100
template<class T>
class Array
{
private:
	T _a[N];
};
int main()
{
	Array<int> a1;  // 100
	Array<int> a2;  // 1000
	return 0;
}

非类型模板参数的使用

 非类型模板参数的使用和类型模板参数的使用类似,也是一样的传参。

#include <iostream>
using namespace std;
//#define N 100
//#define N 1000

//类型模板参数,非类型模板参数
template<class T, int N>
class Array
{
private:
	T _a[N];
};
int main()
{
	Array<int, 100> a1;  // 100
	Array<int, 1000> a2;  // 1000
	cout << sizeof(a1) << endl;
	cout << sizeof(a2) << endl;
	return 0;
}

  这里的N是常量,不能修改的,因为数组的大小是常量。

template<class T, int N>
class Array 
{
public:
	Array() 
	{
		N = 10; //不能修改
	}
private:
	T _a[N];
};

int main()
{
	Array<int, 100> a1;  // 100
	Array<int, 1000> a2;  // 1000
	cout << sizeof(a1) << endl;
	cout << sizeof(a2) << endl;
	return 0;
}

 注意点:

记住一点:模板参数不一定全部传类型,也有可能传整型来固定大小啊。非类型模板参数是一个参数,不是什么类型都能做非类型模板参数的。

double 和 自定义类型等不能作为非类型模板参数。

#include <iostream>
using namespace std;

template<class T, char ch> 
//char short可以做非类型模板参数,还有long long / long / int / short / char 整型家族

//以下类型不能作为非类型模板参数
template<class T, double D> 
template<class T, string s>
class B
{};

模板的特化

模板的特化:针对某些类型的特殊化处理

函数模板的特化

针对某一种模板的具体类型,要根原来的模板做不一样的处理,就写一个特化,以下是函数模板的写法,针对const char* 类型进行特殊处理。

#include <iostream>
#include <string>
using namespace std;
//原模板
template<class T>
bool IsEqual(T& left, T& right)
{
	return left == right;
}
//模板的特化:针对某些类型的特殊化处理
template<>
bool IsEqual<const char*>(const char*& left, const char*& right)
{
	return strcmp(left, right) == 0;
}
int main()
{
	int a = 0, b = 1;
	cout << IsEqual(a, b) << endl;
	//这里是指针在比较,而是想比较ASCII码的值,这种情况下就需要使用到特化
	const char* p1 = "hello";
	const char* p2 = "world";
	cout << IsEqual(p1, p2) << endl;
	return 0;
}

类模板的特化

#include <iostream>
using namespace std;
//原类模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//特化的类模板
template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "特化的:Date<int, char>" << endl;
	}
private:
	int _d1;
	char _d2;
};
int main()
{
	Date<int, int> d1;
	Date<int, char> d2;
	return 0;
}

全特化与偏特化

全特化:就是针对特定的一组完整的模板参数,进行完全特殊的处理,为其提供专门的实现,不再使用通用模板的实现方式。

 

偏特化:就是对部分模板参数进行特殊处理,比如只针对其中一个或几个参数进行特化,或者对参数满足特定条件时进行特殊处理,而其他未特化的参数仍然保持一定的通用性。

注意:第一个参数匹配上了,优先使用偏特化,再看第二个参数。

#include <iostream>
using namespace std;
//原模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "原模板:Date<T1, T2>" << endl;
	}
};
//全特化
template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "全特化:Date<int, char>" << endl;
	}
};
//偏特化(半特化)
template<class T2>
class Date<int, T2>
{
public:
	Date()
	{
		cout << "偏特化:Date<int, T2>" << endl;
	}
};


int main()
{
	Date<int, int> d1;  //偏特化
	Date<int, char> d2; // 全特化
	Date<int, double> d3; //偏特化
	Date<char, double> d4; //原模板
	return 0;
}

偏特化的其它情况

还有一种情况的特化,对指针,或者引用的特化,也是一种特化。

#include <iostream>
using namespace std;
//原模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "原模板: Date(T1, T2)" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
//特化模板,两个指针的特化,不管什么类型,只要是指针就调用该模板
template<class T1, class T2>
class Date<T1*, T2*>
{
public:
	Date()
	{
		cout << "特化都是指针:Date(T1*, T2*)" << endl;
	}
};
//特化的都是引用
template<class T1, class T2>
class Date<T1&, T2&>
{
public:
	Date()
	{
		cout << "特化都是引用:Date(T1&, T2&)" << endl;
	}
};
int main()
{
	Date<int, int> d1;
	Date <int*, char*> d2;
	Date <char*, double*> d3;


	Date<int&, int&> d4;
	Date<double&, char&> d5;
	return 0;
}

模板的分离编译

什么是分离编译

  • 项目工程中一般将函数或者类的声明放到.h,将函数或者类的定义放到.cpp

为什么要分离编译

在一个项目工程中,代码不仅仅是我们平时写的几百行代码,而是几万甚至十几万行代码,分离编译的好处就是方便查看和维护。

为什么模板不能分离编译

普通的类和函数都是可以分离编译的。

这就是我们在实现前面所学的容器的时候,类模板和函数模板都写在一个.h文件中的,因为不能分离编译

同样是分离编译,普通函数/类可以,函数模板、类模板为什么不行??? 

和编译链接有关系,实际项目中有很多文件,Func.h  Func.cpp  Test.cpp 以三个文件为例。.

编译链接有哪些过程?

Linux环境下为例,生成的后缀都是Linux中的文件后缀

1.预处理:展开头文件,宏替换,条件编译,去掉注释

        生成 Func.i  Test.i

2.编译:检查语法,生成汇编代码

        生成Func.s  Test.s

3.汇编:将汇编代码转成二进制的机器码

        生成Func.o  Test.o

4.链接:将两个obj目标文件链接起来

        生成 a.out,也可以指定名字,没有指定生成的就是a.out

链接的时候到底做了什么??

汇编代码和二进制的机器码几乎是一一对应的,只不过我们看不懂二进制的机器码,所以我这里就看汇编代码,来进行理解。这里 call,机器码会用一段指令一一对应,只不过我们这里用汇编方便理解。

call之前填不上地址,为什么填不上地址

因为只包含了声明,没有具体实现,只有当定义(cpp文件)的时候才会有地址。

 编译的过程从i  -> s  -> o,从头到尾只有声明,都没有它的地址,参数都是能匹配上的,所以这里填问号。

一、函数调用的一般过程

在 C++ 中,函数的调用通常经历预处理、编译、汇编和链接等阶段。

对于普通函数,如 F1,在预处理阶段,头文件被展开。在编译阶段,当看到对F1的调用时,编译器会在符号表中记录这个未解析的符号,同时根据函数声明确定函数的参数和返回值类型等信息。在汇编阶段,会生成相应的汇编代码,但此时函数的具体地址还未确定。在链接阶段,链接器会在所有的目标文件和库文件中查找F1的实现,如果找到了,就会将其地址填入符号表中的相应位置,完成链接。

二、函数模板的特殊情况

对于函数模板F2,情况则有所不同。

  1. 头文件中的声明:在头文件(如 Func.h)中,只包含了函数模板的声明:

    在头文件中,只是包含了模板函数的声明,不会把具体调用时的参数值(比如这里的 10)传递给头文件。

    头文件只是提供了一个模板的框架和接口声明,当在某个源文件(如 test.cpp)中进行实际调用时,编译器根据调用时传入的具体参数来确定模板参数的类型,但这个具体的参数值并不会传递到头文件中去。

      template<class T>
       void F2(const T& x);
    这里只是提供了一个模板的框架,并不针对特定的类型进行实例化,也不知道具体的类型参数是什么。
  2. test.cpp中的调用:当在test.cpp中看到对F2(10)的调用时,在预处理阶段,头文件被展开,引入函数模板的声明。在编译阶段,编译器知道这里调用了一个模板函数,并记录下这个调用以及传入的参数类型(这里是int),但此时它并不能确定最终的函数实现。因为模板的本质是一种代码生成机制,只有在确定了具体的类型参数后,才能生成真正的函数代码。在汇编阶段,由于不知道具体类型,所以无法确定函数的具体地址,只能在符号表中记录一个未解析的符号,标记这个地方需要在链接阶段找到具体的函数地址。

  3. Func.cpp中的情况:在编译Func.cpp时,由于不知道会有哪些具体的类型参数传入模板函数,所以无法针对特定的类型进行实例化生成具体的函数代码。

  4. 链接阶段的问题:在链接阶段,链接器会查找所有的目标文件和库文件,试图解析未定义的符号。对于普通函数,只要在某个编译单元中有其实现,就可以找到并填入地址。但对于函数模板,由于在编译阶段没有针对特定类型进行实例化,链接器也无法确定具体的函数地址。即使在test.cpp中调用了F2(10),链接器也不能从这个调用中推断出在Func.cpp中应该实例化出针对int类型的函数模板版本。

三、总结

为什么 F1 可以找到 call 所需的地址,而F2 找不到呢???

对于普通函数 F1,在链接阶段,链接器可以在各个编译单元中找到其实现,因为普通函数的实现是确定的,不依赖于特定的类型参数,所以可以顺利地在符号表中填入正确的地址完成链接。

而对于函数模板F2,在声明的头文件中只是提供了模板的框架,不知道具体的类型参数。在test.cpp中调用F2(10)时虽然实例化成了int类型,但这个信息并不能自动传递到Func.cpp中去进行实例化。在编译Func.cpp时,由于不知道具体的类型参数,无法实例化出具体的函数,所以在链接阶段,链接器找不到针对特定类型实例化后的函数地址,最终在符号表中没有 F2 的有效地址,从而出现链接错误。

所以,虽然在test.cpp中调用F2(10)传入了int类型,但这个信息在编译和链接的过程中并不会自动传递到头文件(如Func.h)或Func.cpp中。这是由于编译和链接过程的独立性以及头文件和链接器的局限性所决定的。函数模板通常不能像普通函数那样简单地进行分离编译,需要在包含模板定义的编译单元中进行实例化,或者通过显式实例化等特殊手段来确保在链接阶段能够找到正确的函数地址。

解决模板不能分离编译的方法

显示实例化

似于特化,int类型可以,但是double又不行了,用一个就得实例化,这种方法不常用。

double 类型的实例化

Template

Void F2<double>(const double& x);

不要分离编译

优点】
1. 模板复用了代码,节省资源,更快的选代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1.模板会导致代码膨胀问题,也会导致编译时间变长
2.出现模板编译错误时,错误信息非常凌乱,不易定位错误

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

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

相关文章

数据定义语言CREATE的应用

新书速览|SQL Server 2022从入门到精通&#xff1a;视频教学超值版_sql server 2022 出版社-CSDN博客 《SQL Server 2022从入门到精通&#xff08;视频教学超值版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) SQL Se…

相交链表 -------------应用

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

2024上海工博会,正运动机器视觉运动控制一体机应用预览(三)

■展会名称&#xff1a; 第二十四届中国国际工业博览会&#xff08;以下简称“上海工博会”&#xff09; ■展会日期 2024年9月24日–28日 ■展馆地点 中国国家会展中心&#xff08;上海&#xff09; ■展位号 6.1H-E261 本次上海工博会&#xff0c;正运动技术将携高性…

微信小程序开发项目

微信小程序是一种轻量级的应用程序&#xff0c;无需下载即可使用&#xff0c;并且具有即用即走的特点。这种新型的应用程序正在改变人们的使用习惯&#xff0c;使得人们可以更加方便快捷地获取信息和服务。本文将详细介绍微信小程序的开发过程&#xff0c;包括开发前的准备、开…

9.3 Linux_文件I/O_相关函数

打开与关闭 1、打开文件 int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);返回值&#xff1a;成功返回文件描述符&#xff0c;失败返回EOF pathname&#xff1a;文件路径 flags&#xff1a;标志&#xff0c;其中O_RDO…

CUDA安装教程+显卡驱动安装

CUDA安装教程显卡驱动安装 新的ubuntu系统&#xff0c;需要重新安装显卡驱动&#xff0c;以及cuda&#xff0c;记录以下坑点&#xff1a; 先安装显卡 nvidia-smi输入后出现 首先知道自己的GPU型号&#xff0c;如RTX 4090 这里下载&#xff1a;https://www.nvidia.com/Down…

自动化学习2:pytest的高级用法(mark标记/fixture/hook)

一.mark的用法 概念&#xff1a;Pytest提供的mark标记&#xff0c;允许我们标记测试函数&#xff0c;测试类和整个模块。通过不同的标记实现不同的运行策略&#xff0c;如标记冒烟测试用例。 1.注册标记 可以在pytest.ini文件注册自定义标记 除了自己注册的标记外&#xff0…

网安新声 | 黎巴嫩BP机爆炸事件带来的安全新挑战与反思

网安加社区【网安新声】栏目&#xff0c;汇聚网络安全领域的权威专家与资深学者&#xff0c;紧跟当下热点安全事件、剖析前沿技术动态及政策导向&#xff0c;以专业视野和前瞻洞察&#xff0c;引领行业共同探讨并应对新挑战的策略与可行路径。 9月17日&#xff0c;黎巴嫩境内发…

一文学会 Java 8 的Predicates

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 在这份详细的指南中&#xff0c;您将了解 Java Predicates&#xff0c;这是 Java 8 中一个新颖且有用的特性。本文解释了 Java Predicates 是什么以及如何在各种情况下使用它们。 在这份详尽的指南中…

828华为云征文|Flexus X实例安装H5ai目录列表程序

828华为云征文&#xff5c;Flexus X实例安装H5ai目录列表程序 引言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 主要使用场景 二、购买Flexus云服务器X实例2.1 购买规格参考2.2 查看Flexus云服务器X实例状态 三、远程连接Flexus云服务器X实例3.1 重置密码3.2 本…

sleuth(micrometer)+zipkin 实现链路追踪

目录 sleuth 介绍 相关术语 Trace Span Annotation 使用 依赖 参考 Zipkin 介绍 使用 下载最新版并使用 客户端集成 参考 sleuth 介绍 Spring Cloud Sleuth 能够跟踪您的请求和消息&#xff0c;以便您可以将该通信与相应的日志条目相关联。 您还可以将跟踪信息…

saltstack高级用法

一、saltstack的高级用法 一、job管理 1、job简介 Jid&#xff1a;job id&#xff0c;格式为%Y%m%d%H%M%S%fmaster在下发指令消息时&#xff0c;会附带上产生的Jid&#xff0c;minion在接收到指令开始执行时&#xff0c;会在本地的cachedir&#xff08;默认是/var/cache/salt/…

git add成功后忘记commit的文件丢了?

本文目标&#xff1a;开发人员&#xff0c;在了解git fsck命令用法的条件下&#xff0c;进行git add成功但由于误操作导致丢失的文件找回&#xff0c;达到找回丢失文件的程度。 文章目录 1 痛点2 解决方案3 总结/练习 1 痛点 开发过程中&#xff0c;分支太多&#xff08;基线分…

网络安全等级保护 | 规范企业网络系统安全使用 | 天锐股份助力等保制度落地

在当今数字化高速发展的时代&#xff0c;网络安全对于企业的重要性日益凸显。而近年来&#xff0c;数据泄露、网络攻击等安全事件频发&#xff0c;给企业和个人带来了前所未有的挑战。在这一背景下&#xff0c;网络安全等级保护制度&#xff08;简称“等保”&#xff09;作为国…

论文 | Reframing Instructional Prompts to GPTk’s Language

作者&#xff1a;Swaroop Mishra, Daniel Khashabi, Chitta Baral, Yejin Choi, Hannaneh Hajishirzi 论文摘要&#xff1a;语言模型 (LM) 更容易遵循哪些类型的指令提示&#xff1f; 我们通过进行广泛的实证分析来研究这个问题&#xff0c;这些分析阐明了成功指令提示的重要特…

gateway--网关

在微服务架构中&#xff0c;Gateway&#xff08;网关&#xff09;是一个至关重要的组件&#xff0c;它扮演着多种关键角色&#xff0c;包括路由、负载均衡、安全控制、监控和日志记录等。 Gateway网关的作用 统一访问入口&#xff1a; Gateway作为微服务的统一入口&#xff0c…

【Unity保龄球项目】的实现逻辑以及代码解释

1.BaoLQManager.cs 这个脚本实现了基本的保龄球游戏逻辑&#xff0c;包括扔球功能。 using System.Collections; using System.Collections.Generic; using UnityEngine;public class BaoLQManager : MonoBehaviour {// 业务逻辑1&#xff1a;把保龄球扔出去// 业务逻辑2&am…

react hooks--useRef

基本用法 在类组件中获取一个dom元素实例&#xff0c;可以通过React.CreateRef或者回调函数的方式去获取。语法&#xff1a;const refContainer useRef(initialValue);使用场景&#xff1a;在 React 中进行 DOM 操作时&#xff0c;用来获取 DOM作用&#xff1a;返回一个带有 …

TensorRT | 在多个GPU中指定推理设备

说实话&#xff0c;之前我在笔记本上都一直都是只有一块N卡&#xff0c;所以没有过多关注过这个问题。然而昨天有个人问我&#xff0c;TensorRT怎么在多个GPU中指定模型推理GPU设备&#xff1f;我查了一下&#xff0c;发现官方有几个不同的解决方案&#xff0c;个人总结了一下&…

面经 | webpack

webpack webpackloader基本语法rules自定义loader 你可以写哪些loader&#xff1f;常见loader pluginwebpack生命周期 [参考](https://blog.csdn.net/qq_17335549/article/details/137561075)常见plugin webpack 一个打包工具&#xff0c;就和npm是一个包管理工具差不多。一般…