当前位置: 首页 > ds >正文

深入浅出之STL源码分析5_类模版实例化与特化

在 C++ 中,​​类模板的实例化(Instantiation)和特化(Specialization)​​ 是模板编程的核心概念,而 ​​显式实例化(Explicit Instantiation)和隐式实例化(Implicit Instantiation)​​ 是实例化的两种具体方式。

模版的特化和实例化是两个不同等级的概念,不能等同。

类模版特化和类模版实例化是两个层面的事,特化还是提供一种特殊的模子,而最终是否生成代码,还要看是否能够进行实例化,只有实例化之后,才能生成真正的代码,后面我会详细的说下区别。

(这里稍微有个特殊的地方,就是函数模版全特化的时候,会生成对应的函数代码,具体可以参考我另外一篇博客 深入浅出之STL源码分析5_实例化与全特化-CSDN博客)

以下是清晰的分辨和对比:

1. 核心概念​

​1.1 类模板的实例化(Instantiation)​

  • ​定义​​:
    将模板的泛型类型参数(T)替换为具体类型(如 intstring),生成具体的类代码的过程。
  • ​本质​​:
    编译器根据模板生成实际的类代码(例如 vector<int> 是由 vector<T> 实例化而来)。

​1.2 类模板的特化(Specialization)​

  • ​定义​​:
    针对特定类型参数,提供与通用模板不同的实现。分为 ​​全特化(Full Specialization)​​ 和 ​​偏特化(Partial Specialization)​​。
  • ​本质​​:
    为特定类型定制模板行为(例如为 bool 类型优化 vector 的存储方式)。

2. 显式实例化 vs 隐式实例化​

模版的实例化分为显示的实例化和隐式的实例化。

模版的实例化指函数模版(类模版)生成模版函数(模板类)的过程,对于函数模版而言,模版实例化之后,会生成一个真正的函数,而类模版实例化后,对于隐式实例化,只是完成了类的定义,类模版的成员函数(包括构造函数)需要到调用的时候,才会被初始化,而如果显示调用,则不论是否调用,都会直接生成普通函数,和类成员函数。

还有最重要的一点就是,我们最后一定要通过查看汇编,来验证我们的结论,也就是最终的汇编代码中有对应的成员函数,才证明生成了对应的代码,如果没有在汇编里生成,证明就是没有作用,这里可以使用 这个线上工具进行验证,Compiler Explorer

2.1.隐式实例化

2.1.1 模版隐式实例化的定义

其中隐式的实例化是我们平时最常用的实例化方式。隐式实例化,或者说按需实例化on-demand,是当我们要用这个模板生成实体的时候,要创建具体对象的时候,才做的实例化。

2.1.2 类模版的隐式实例化

类模版隐式实例化是指(on-demand),在使用模版类时,才将模版实例化,相对于类模板显示实例化而言的。

看下面具体的代码例子:

#include<iostream>
using namespace std;
template <typename T1,typename T2>
class A{
public:A(){}void Max(T1 t1,T2 t2){}T1 Aa;T2 Bb;
};
int main(int argc,char *argv[]){A<double ,double> temp;//只有这一种调用方式,也就是说只能是显示调用.// A<>temp1; // 这个是会报错的。//temp.Max(3.4,4.5);return 0;
}

我们用Compiler Explorer 进行查看汇编数据,会很清晰,对于on_demand实例化,或者隐式实例化

我们有个概念叫,成员函数​​惰性生成​​。也就是调用的时候,才会生成,比方上面的例子中,//temp.Max(3.4,4.5);注释掉后,只是会调用构造函数。

汇编结果如下,符合我们的预期:

而当我们把第二个Max函数的调用放开后,结果如下:

2.2.显示实例化

2.2.1 模版显示实例化的定义

显示实例化,也称为外部实例化,在不使用类模版的时候,将类模版实例化,

显示实例化不是按需的,也就是不论是否调用对应的类成员函数,都会生成对应的成员函数。

2.2.2 类模版的显示实例化

对于类模版而言,不管是否生成一个模版类对象,都可以直接通过显示实例化声明,将类模板实例化为模版类。

格式为:

template class [类模版名]

#include<iostream>
using namespace std;template <typename T1,typename T2>
class A{
public:void Max(T1 t1,T2 t2){}T1 Aa;T2 Bb;
};// template class A<int ,int>;int main(int argc,char *argv[]){return 0;
}

然后用指令把它生成汇编语言

g++ -std=c++11 -S main.cpp -o main.s

我们查看这个文件 main.s 里面是找不到 class A Max 函数。

我们还是直接用工具进行验证,这样的方式比较直观一些:

而如果我们把上面的注释去掉:

#include<iostream>
using namespace std;template <typename T1,typename T2>
class A{
public:void Max(T1 t1,T2 t2){}T1 Aa;T2 Bb;
};template class A<int ,int>;int main(int argc,char *argv[]){return 0;
}

g++ -std=c++11 -S main.cpp -o main.s

我们查看这个文件 main.s 里面是能找到 class A func 函数和类A的Max成员函数。

说明已经实例化成功了。

3. 特化(Specialization)

所谓特化,就是将泛型的东西搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰(例如const或者摇身一变成为了指针之类的东东,甚至是经过别的模板类包装之后的模板类型,或者模版个数由多变少,由...Args变为,T 和 ...Args)或完全被指定了下来。

如果被完全的制定了下来,就叫做全特化,剩余的都叫做偏特化。

3.1. 特化与泛化

在之前的coding中, 我们可能并没有听说过什么特化还有泛化的概念,那么今天,我将来总结介绍这一“新”概念!泛化与特化的概念是一对反义词!所谓,

泛化:泛化其实就是泛型化(通用化)的意思,其实就是让你的代码通用性更高!更适合多种应用场景!

格式:(其实就是定义类模板/函数模板时候的代码格式啦~)

//下面是泛化版本的模板类 or 模板函数的definition//类模板definition
template<typename T1,typename T2,...typename Tn>
class templateClassName{
public://...
}
//函数模板的definition
template<typename T1,typename T2,...typename Tn>
void templateFuncName(Params){//...
}

特化:特化其实就是对于特殊的类型(类型模板参数)进行特殊的对待,给它开小灶,给它只适合它自己用的专用代码。特化又分为全特化和偏特化!

全特化:将类模板/函数模板的模板参数列表中的all模板参数做特殊化!

格式:(全特化时,类型模板参数列表为空!)

//下面是全特化版本的类模版 or 函数模版的definition
template<>
//全特化版本的类模版的definition
class templateClassName<给要特化的all模板类型参数do具体化>{
public://...
}
//全特化版本的模板函数的definition
template<>
void templateFuncName(具体的Params){//...
}

小细节之全特化的标识:template<>,看到这个标识时,你就要立马反应过来这里是在做全特化的工作(要记住wor~)

偏特化:将类模板/函数模板的模板参数列表中的部分模板参数做特殊化!

(那么为什么要进行偏特化呢?我认为,其实这就是因为偏特化会提高我模板代码的效率!你肯定是把其中经常用到的某种参数类型或者特殊的参数类型特化的,这样遇到这种模板参数适你的模板就可以进行特殊的代码处理了,进而可提高效率!否则你也不需要用到特化吧对吧?)

注意①:在写类模板/函数模板的特化版本前,必须要写出该模板的泛化版本!这一点务必要记住!(没有泛化版本根本就不可以写出特化版本的)

注意②:编译器会优先选择特化版本的类模板/函数模板的代码,也就是说,一旦你定义了一个类模板/函数模板的泛化版本以及对应的特化版本,如果你调用该类模板/函数模板时所传入的模板参数列表对应了特化版本的代码时,这时编译器就会优先将你特化版本的代码覆盖掉泛化版本的代码,进而使用特化版本的代码来执行程序~

注意③:

当类模板/函数模板进行全特化之后,这个全特化后的类/函数就不是一个模板类/函数了

(因为全特化完成后,模板参数类型为空了,即template<>了,就是一个具体的类/函数)

当类模板/函数模板进行偏特化之后,这个偏特化后的类/函数仍然是一个类模版/函数模版

(因为偏(局部)特化完成后,模板参数类型仍然不为空,即template,就还是一个模板类/函数)

但是同时注意的是,虽然全特化已经是没有模版参数化了,但是它仍然需要实例化的时候,调用到这个类,也就是生成类对象的时候,才会在汇编层面产生,对应的构造函数,和成员函数,如果不进行实例化的话,那么和泛化版本,和偏特话版本,没有任何的区别。

这也说明 特化和实例化是两个层面的事,特化还是提供一种特殊的模子,而最终是否生成代码,还要看是否能够进行实例化,只有实例化之后,才能生成真正的代码。(但是这里函数模版的全特化版本有点特殊,只要全特化了就会生成对应的函数)

3.2. 类模板特化

先给出类模板的泛化版本:

#include<iostream>
using namespace std;
template <typename T1,typename T2>
class A{
public:A(){}void Max(T1 t1,T2 t2){}T1 Aa;T2 Bb;
};

3.2.1 类模板全特化

3.2.1.1 常规全特化

废话不多说,请看以下代码:

#include<iostream>
using namespace std;
template <typename T1,typename T2>
class A{
public:A(){}void Max(T1 t1,T2 t2){}T1 Aa;T2 Bb;
};
template<>
class A<int,int>{public:A(){        }void Max(int t1,int t2){std::cout<<"i am 全特化版本,int,int"<<std::endl;}int Aa;int Bb;
};
int main(int argc,char *argv[]){A<int,int>temp;temp.Max(2,3);return 0;
}

运行结果如下:

我们来看下上面的代码中,对应的汇编代码:

那么如果我不进行调用(按需实例化),那么在汇编层面能否生成代码呢,我们看下,

结果是不会生成任何代码,这也说明了实例化和特化还是两个不同层面的概念。

3.2.1.2 特化类模板的成员函数而不是类模板本身

我们可以不特化类模板,而仅仅是去特化类模板的成员函数。这样,当你创建该类模板对象时,虽然是调用的泛化版本的构造函数和析构函数去创建和释放它,但是如果说你定义了该泛化版本的对象但你这个对象定义时所提供的模板参数与该特化成员函数的模板参数列表一致时,那么编译器就会优先调用该特化版本的成员函数(当你这个对象使用到该成员函数时候),而不是泛化版本的成员函数。

废话不多说,请看以下代码:

#include<iostream>
using namespace std;template <typename T1,typename T2>
class A{
public:A(){std::cout<<"i am general constructor"<<std::endl;}void Max(T1 t1,T2 t2){std::cout<<"i am general version"<<std::endl;}T1 Aa;T2 Bb;
};
template<>
void A<int,int>::Max(int t1,int t2){std::cout<<"i am full specification,int,int"<<std::endl;}int main(int argc,char *argv[]){A<int,int>temp;temp.Max(2,3);return 0;
}

3.2.2 类模板偏特化(局部特化)

3.2.2.1 从模板参数数量这个角度来进行偏特化

请看以下代码:

#include<iostream>
using namespace std;template <typename T1,typename T2>
class A{
public:A(){std::cout<<"i am general constructor"<<std::endl;}void Max(T1 t1,T2 t2){std::cout<<"i am general version"<<std::endl;}T1 Aa;T2 Bb;
};
template<typename T2>
class A<int,T2>{public:A(){std::cout<<"i am part specification constructor"<<std::endl;}void Max(int t1,T2 t2){std::cout<<"i am part specification Max"<<std::endl;}int Aa;T2 Bb;
};int main(int argc,char *argv[]){A<int,int>temp;temp.Max(2,3);return 0;
}

运行结果:

全特化的标识是template<>,表示all我模板参数我都特殊化了,而不是把all的模板参数都列出来!模板参数一旦在模板参数列表列出来,就表明这个模板参数我不想特化的意思!!!

3.2.2.2 从模板参数范围这个角度来进行偏特化

 什么叫做参数范围呢?参数的范围既能缩小又能增大。

//举例:
//本来参数类型是int:
//参数范围缩小:
int->const int
int->int*
int->int&
int->int&&
int->const int*
//参数范围增大:
const int->int
int*->int
int&->int
int&&->int

了解了模板参数范围的概念后,下面就用参数的范围来do类模板的偏特化~

请看以下代码:

#include<iostream>
using namespace std;
template<typename T>
class TC {//泛化的TC类版本(带1个模板参数)
public:void testfunc() {cout << "泛化版本!" << endl;}
};
//从模板参数范围上进行的类模板的特化版本//告诉编译器,如果模板参数类型你传入的是const类型,那就优先用该特化版本的类模板代码
template<typename T>
class TC<const T> {//TC<const T>特化版本
public:void testfunc() {cout << "TC<const T>特化版本!" << endl;}
};
//告诉编译器,如果模板参数类型你传入的是指针类型,那就优先用该特化版本的类模板代码
template<typename T>
class TC<T*> {//TC<T*>特化版本
public:void testfunc() {cout << "TC<T*>特化版本!" << endl;}
};
//告诉编译器,如果模板参数类型你传入的是左值引用类型,那就优先用该特化版本的类模板代码
template<typename T>
class TC<T&> {//TC<T&>特化版本
public:void testfunc() {cout << "TC<T&>特化版本!" << endl;}
};
//告诉编译器,如果模板参数类型你传入的是右值引用类型,那就优先用该特化版本的类模板代码
template<typename T>
class TC<T&&> {//TC<T&&>特化版本
public:void testfunc() {cout << "TC<T&&>特化版本!" << endl;}
};
int main(void) {TC<const int> tcint;//调用TC<const T>特化版本的代码!tcint.testfunc();TC<const double> tcdouble;//调用TC<const T>特化版本的代码!tcdouble.testfunc();cout << "---------------------------" << endl;TC<int*> tcintPoint;//调用TC<T*>特化版本的代码!tcintPoint.testfunc();TC<double*> tcdoublePoint;//调用TC<T*>特化版本的代码!tcdoublePoint.testfunc();cout << "---------------------------" << endl;TC<int&> tcintAnd;//调用TC<T&>特化版本的代码!tcintAnd.testfunc();TC<double&> tcdoubleAnd;//调用TC<T&>特化版本的代码!tcdoubleAnd.testfunc();cout << "---------------------------" << endl;TC<int&&> tcintAndAnd;//调用TC<T&&>特化版本的代码!tcintAndAnd.testfunc();TC<double&&> tcdoubleAndAnd;//调用TC<T&&>特化版本的代码!tcdoubleAndAnd.testfunc();return 0;
}

输出的结果如下:

4. 对比表格​

  • 概念类模版实例化(Instantiation)类模版特化(Specialization)
    ​目的​生成具体类型的模板代码为特定类型定制模板行为
    ​是否生成新代码​不生成,需要实例化才能触发
    ​语法​

    隐式:生成对象,调用成员函数

    显式:template class Name<Type>

    template<> class Name<Type>
    实例化方式隐式实例化显式实例化
    ​触发条件​首次使用模板手动声明 template class ...
    ​编译时间影响​可能增加编译时间可优化编译时间
    ​代码位置​任何使用模板的地方通常在 .cpp 文件中

http://www.xdnf.cn/news/5504.html

相关文章:

  • RAG与语义搜索:让大模型成为测试工程师的智能助手
  • DVWA靶场Cryptography模块medium不看原码做法
  • Python时间模块
  • MySQL 从入门到精通(二):DML 数据操作与 DQL 数据查询详解
  • Python项目75:PyInstaller+Tkinter+subprocess打包工具1.0(安排 !!)
  • 阿里云OSS-服务端加签直传说明/示例(SpringBoot)
  • Python数据分析案例75——基于图神经网络的交通路段流量时间序列预测
  • navicat 如何导出数据库表 的这些信息 字段名 类型 描述
  • fota移植包合入后编译验证提示:File verification failed
  • Java线程池深度解析:从使用到原理全面掌握
  • KTOR for windows:無文件落地HTTP服务扫描工具
  • 【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)高级用法(二)
  • 教育行业的 RAG 落地:个性化学习助手设计
  • 【Linux基础】网络相关命令
  • Client 和 Server 的关系理解
  • Yocto项目实战经验总结:从入门到高级的全面概览
  • 大模型Embedding模型介绍与使用
  • [CANN] 安装软件依赖
  • 数仓-可累计,半累加,不可累加指标,是什么,举例说明及解决方案
  • 前端面试题:说说你对 Vue 中异步组件的理解
  • jetson orin nano super AI模型部署之路(十)使用frp配置内网穿透,随时随地ssh到机器
  • 单词怎么记:以use一词为例
  • Java中Comparator排序原理详解
  • 3. 无重复字符的最长子串(滑动窗口)
  • 客户端建立一个连接需要占用客户端的端口吗
  • NHANES稀有指标推荐:HALP score
  • average per-pixel disparity error: EPE及不同距离值下的误差曲线
  • JavaScript基础-全局作用域
  • 《Python星球日记》 第53天:卷积神经网络(CNN)入门
  • DNS服务实验