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

目录

非类型模版参数

类型模板参数

非类型模板参数

非类型模板参数的使用

模板的特化

函数模板的特化

类模板的特化

全特化与偏特化

偏特化的其它情况

模板的分离编译

什么是分离编译

为什么要分离编译

为什么模板不能分离编译

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

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

编译链接有哪些过程?

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

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

显示实例化

不要分离编译


非类型模版参数

在 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;  // 100Array<int> a2;  // 1000return 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;  // 100Array<int, 1000> a2;  // 1000cout << 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;  // 100Array<int, 1000> a2;  // 1000cout << 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.xdnf.cn/news/147920.html

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

相关文章

那年我双手插兜,使用IPv6+DDNS动态域名解析访问NAS

估计有很多科技宅和我一样&#xff0c;会买一个NAS存储或者自己折腾刷一下黑群晖玩玩&#xff0c;由于运营商不给分配固定的公网IP&#xff0c;就导致我在外出的时候无法访问家里的NAS&#xff0c;于是远程访问常常受到IP地址频繁变动的困扰。为了解决这一问题&#xff0c;结合…

面试知识点总结篇一

一、C语言和C有什么区别 C语言是面向过程&#xff0c;强调用函数将问题分解为多个子任务&#xff0c;按顺序逐步进行。数据和操作分开C则是面向对象&#xff0c;面向对象是一种基于对象和类的编程范式&#xff0c;关注如何利用对象来抽象和模拟现实世界的实体。因此引入了类&a…

docker部署datart并添加扩展clickhouseodps的jar包数据源驱动

近期部门有个小需求&#xff0c;针对所有产品线的用户访问记录日志需要一个看板展示&#xff0c;于是在找有没有开源的项目不用自己开发的产品直接部署&#xff0c;千挑万选发现一个叫datart的产品能自定义编写sql展示想要展示的数据&#xff0c;于是开始了datart的搭建部署&am…

大厂真题-Kafka为什么这么快之零拷贝

一、零拷贝技术的背景 在传统的数据传输过程中&#xff0c;当需要将磁盘中的数据发送到远程服务器时&#xff0c;数据通常需要经过多次拷贝和上下文切换。具体来说&#xff0c;这些步骤包括&#xff1a; 四次拷贝 从硬盘到内核缓冲区&#xff1a; 当用户进程通过read()系统调…

【HTML5】html5开篇基础(2)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

Python中requests模块(爬虫)基本使用

Python的requests模块是一个非常流行的HTTP库&#xff0c;用于发送HTTP/1.1请求。 一、模块导入 1、requests模块的下载&#xff1a; 使用包管理器下载&#xff0c;在cmd窗口&#xff0c;或者在项目的虚拟环境目录下&#xff1a; pip3 install -i https://pypi.tuna.tsingh…

DC-DC选型

Buck、Boost、Buck-boost 同步非同步 隔离与非隔离 电源效率 模式选择 选型 总结

【机器学习】TensorFlow编程基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TensorFlow编程基础张量(Tensor)计算图(Computational Graph)会话(Session)基本…

数据包签名校验的Web安全测试实践

01 测试场景 在金融类的Web安全测试中&#xff0c;经常可以见到Web请求和响应数据包加密和签名保护&#xff0c;由于参数不可见&#xff0c;不能重放请求包&#xff0c;这类应用通常不能直接进行有效的安全测试&#xff0c;爬虫也爬不到数据。 02 解决思路 对于这类应用&am…

STaR: Bootstrapping Reasoning With Reasoning

STaR: Bootstrapping Reasoning With Reasoning 基本信息 博客贡献人 燕青 作者 Eric Zelikman, Yuhuai Wu, Jesse Mu, et al. from Stanford University and Google Research 标签 Large Language Model, Chain-of-thought, Fine-tuning 摘要 生成逐步的“思维链”逻…

揭秘!高校如何逆袭,在算法与科技竞技场中脱颖而出?

目录 揭秘!高校如何逆袭,在算法与科技竞技场中脱颖而出? 一、算法秘境:深度挖掘,教学相长 二、跨界融合:场景为王,合作共赢 企业和高校之间在:场景,算法,数据,算力的优势,高校优势不明显,仅仅在算法方面存在一些优势但并不明显。高校怎样做 揭秘!高校如何逆袭…

2024最新盘点:国内外主流的10款流程管理系统!

本文将盘点十款流程管理系统&#xff0c;为企业选型提供参考&#xff01; 想象一下&#xff0c;在一个企业中&#xff0c;各个部门的工作流程混乱&#xff0c;审批环节繁琐&#xff0c;信息传递不及时。这时&#xff0c;流程管理系统就如同一位高效的指挥官&#xff0c;将企业的…

软件测试干了5年,都白忙活了。。。

本科非计算机专业&#xff0c;在深圳做了5年软件测试工作&#xff0c;从一开始一脸懵的点点点&#xff0c;到现在会自动化测试了&#xff0c;浅谈一下从事软件测试的一点点心得体会&#xff0c;仅供参考交流。如果你本科且非计算机专业的话可以试下&#xff08;但就目前环境建议…

使用Fiddler Classic抓包工具批量下载音频资料

1. 通过F12开发者工具&#xff0c;下载音频文件 浏览器打开音频列表->F12快捷键->网络->媒体&#xff0c;播放一个音频文件&#xff0c;右边媒体下生成一个音频文件&#xff0c;右击“在新标签页中打开”&#xff0c;可以下载这个音频文件。 2.通过Fiddler Classic抓…

简单题100. 相同的树 (python)20240922

问题描述&#xff1a; python&#xff1a; # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right rightclass Solution(object)…

浅谈EXT2文件系统----inode table

Inode table概述 在 EXT2 文件系统中&#xff0c;inode 表&#xff08;Inode Table&#xff09;是一个非常重要的结构&#xff0c;用于存储文件和目录的元数据。每个文件和目录都由一个 inode&#xff08;索引节点&#xff09;来表示&#xff0c;inode 中包含了关于该文件或目…

中兴交换机三层配置

中兴交换机三层配置 目的&#xff1a;将1-10端口划分到3001vlan&#xff0c;11-20端口划分到3002vlan中去 客户端客户端IPvlan网关主机A88.88.1.1203001192.168.1.254主机B192.168.100.1303002192.168.100.254 1、通过Console线登录设备 **********************************…

CCPQT:2024年10月珠海学术会议

第三届计算、通信、感知与量子技术国际会议&#xff08;CCPQT 2024&#xff09; The 3rd International Conference on Computing, Communication, Perception and Quantum Technology 会议地点&#xff1a;中国珠海 会议时间&#xff1a;2024年10月25日-27日 主办单位&…

D盘格式化了,数据怎么恢复?

在日常使用电脑的过程中&#xff0c;我们有时可能会遇到一些意外情况&#xff0c;比如不小心格式化了D盘&#xff0c;导致重要数据丢失。面对这种情况&#xff0c;很多人可能会感到手足无措&#xff0c;不知道该如何恢复丢失的数据。其实&#xff0c;只要掌握正确的方法&#x…

精益生产管理咨询公司哪家好?这份攻略请拿好

面对市场上琳琅满目的精益生产管理咨询公司&#xff0c;企业往往难以抉择&#xff1a;精益生产管理咨询公司哪家好&#xff1f;本文&#xff0c;天行健咨询将从专业性、实战经验、定制化服务、持续支持等多个维度&#xff0c;深入探讨如何评估并选择一家优秀的精益生产管理咨询…