C++ 模板

server/2024/11/15 0:35:30/

模板的原理

  1. 模板是编译器在编译时进行代码生成的过程。编译器会根据模板的参数类型,生成对应的具体代码。

  2. 模板参数化可以是类型参数,也可以是非类型参数(如整数常量)。编译器会为每种不同的参数类型生成专门的代码实例。

  3. 模板通过函数重载和类型推导的机制来确定使用哪个模板实例。编译器会在编译时选择合适的模板实例。

  4. 模板中的代码并不会真正编译,而是等到实际使用模板时才会被编译。这种延迟编译可以减少编译时间,并提高可移植性。

  5. 模板的实现依赖于编译器的支持,需要编译器具有模板处理的相关能力。不同编译器在模板处理方面可能有差异。

  6. 模板的主要优点是可以实现代码的参数化和复用,使得代码更加通用灵活。但同时也带来了额外的编译复杂度和潜在的错误。

函数模板

模板的实例化

当编写一个函数模板时,你实际上在定义一个函数的框架,而不是具体实现。这个框架可以用不同的类型参数来具体化,每一个具体的类型或类型组合被用来实例化模板的时候,编译器会生成一个对应的函数实例。

隐式实例化

函数模板的隐式实例化是指在代码中使用模板函数时不需要显式地指明具体的类型参数。编译器会根据函数调用中实际传入的参数类型自动推导出模板参数类型,然后生成对应的函数实例。这极大地简化了模板函数的使用过程,因为你不需要为每种数据类型都显式实例化一个函数。

例如:

template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}int main() {// 隐式实例化max函数为int类型int i = max(1, 2);// 隐式实例化max函数为double类型double d = max(3.5, 2.5);
}

显式实例化

函数模板的显式实例化是指在调用模板函数时,明确指出模板的类型参数。这与隐式实例化不同,在显式实例化中,你必须在函数名后面使用尖括号 < > 来指定具体的数据类型。这样做可以消除编译器在实例化时的猜测或自动推导过程,确保准确生成你所需要的函数类型。

例如,若有一个模板函数用于比较两个数的大小:

template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}

进行显式实例化的调用如下:

int main() {// 显式实例化为int类型int result1 = max<int>(3, 5);// 显式实例化为double类型double result2 = max<double>(3.14, 2.71);// ... 其他代码 ...
}

显式实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,编译器会报错

函数模板匹配原则

  1. 确切匹配:编译器首先尝试查找参数类型与函数模板参数类型完全一致的实例化。

  2. 提升匹配:如果没有找到确切的匹配,编译器会尝试将实参提升到一个更高级或更通用的类型来找匹配。

  3. 标准转换匹配:如果提升没有成功,编译器会使用标准类型转换(例如,从intfloat,或者从非常量到常量),尝试找到匹配的函数模板。

  4. 用户定义的转换匹配:如果标准转换也不行,编译器会尝试所有用户定义的转换。

  5. 模板参数推导:在模板匹配过程中,编译器会根据实参类型来推导模板类型参数。如果可以成功推导出模板参数,并且所生成的特例化函数是一个有效的匹配,则选择这个函数。

  6. 模板特化匹配:如果有多个模板可以匹配,且其中一个是特化版本,那么特化版本将优先考虑。因为特化版本提供了更“特定”的匹配。

  7. 重载解析:如果有多个重载函数和函数模板可以匹配,重载解析规则决定最佳选择。该规则考虑了参数类型的匹配优先级和是否有模板参数推导。

模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。

如果经过所有这些尝试后编译器还不能确定哪个函数是最佳匹配,那么它将报错,指出有歧义的函数调用,因为它找到了多个同等好的匹配项。

在某些情况下,函数模板可能会与正常的非模板函数发生冲突,这时通常会优先选择非模板函数,因为它通常是为特定类型专门提供的。

类模板

类模板实例化需要在类模板名字后面加 < > 然后将实例化的类型放在 < >中即可。

非类型模板参数 / 类型模板参数

类型模板参数

出现在模板参数列表中,跟在 class 或 typename关键字后的 参数类型名称

非类型模板参数

它允许模板除了接受类型参数外,还能接受一个或多个编译时常量值作为参数。

这些参数可以是整数、枚举、指针、引用、以及某些情况下的浮点数和字符串字面量。

非类型模板参数在模板实例化时需要用具体的常量表达式来替换,给定的值必须在编译时已知,而不能是运行时确定的值。这是因为模板的实例化发生在编译时,编译器需要知道所有的模板参数以生成具体的代码。

模板的特化

函数模板特化

函数模板特化(Function Template Specialization)是C++中的一个高级特性,允许您为特定类型或值集创建特定的函数实现。这意味着当使用这些特定的类型或值集调用函数模板时,编译器将使用特化的实现,而不是通用的模板实现。

特化主要用于两种情况:

  1. 当模板的通用实现对于某些类型或值集不适用或者效率不高时。
  2. 当需要针对某些类型提供不同的算法或行为时。

特化函数模板需要提供完整的函数定义,并用关键词 "template<>" 前缀表示这是一个特化,然后紧跟着特化的类型。

假如我们有一个通用的函数模板:

template <typename T>
T max(T x, T y) {return (x > y) ? x : y;
}

然后,假设对于char类型,我们想要一个特化的实现,那么可以这样写:

template <>
char max(char x, char y) {// 特殊情况的实现,这里简单返回较大的ASCII值return (x > y) ? x : y;
}

类模板特化 

类模板特化是C++模板编程中一个非常重要的特性,它允许针对某些特定的类型提供一个特定的实现。与函数模板特化类似,类模板特化的目的是在特定情况下提供更高效或更适合的实现。类模板特化可以是全特化,也可以是偏特化。

全特化

当你为模板提供了所有的模板参数的具体类型或值时,就构成了一个全特化。全特化事实上是告诉编译器:对于这种特定的类型组合,使用这个特别定制的类定义,而不是通用模板定义。

例:

template <typename T>
class MyClass { // 通用模板
public:void myFunction() {cout << "Generic template" << endl;}
};// 全特化版本
template <>
class MyClass<int> { // 特化为int类型
public:void myFunction() {cout << "Specialization for int" << endl;}
};

偏特化 

与全特化不同,偏特化是对模板的一部分参数进行特化。它允许你为模板的一部分参数指定具体的类型,而另一部分保留为模板参数。偏特化提供了更高的灵活性,允许在更多的情况下提供特殊的实现。

例:

///
//ex1:template <typename T>
class MyClass<T*> { // 针对所有指针类型的特化
public:void myFunction() {cout << "Specialization for pointers" << endl;}
};///
//ex2:template <typename T1, typename T2>
class MyClass {// 通用的类模板定义
};template <typename T>
class MyClass<int, T> {// 针对第一个参数为int类型的偏特化类模板定义
};

 模板的分离编译问题

为什么不支持分离编译?

原理是这样的:模板本身并不直接生成代码,它们只是一个像模子一样的蓝图(template,字面意思也是"模板"),当你用特定的类型实例化模板时,编译器才根据这个蓝图生成具体的代码。由于编译器需要知道所有模板参数的具体类型才能进行这一过程,这意味着在模板的定义必须对编译器可见,即模板的实现代码(通常是.h文件)必须在任何使用它的地方之前被包含。

这样就导致了几个问题:

  1. 模板的重复编译:如果多个源文件都使用到了相同的模板实例,那么每个源文件在编译时都会生成该模板实例的代码,这会造成编译时间的增加。
  2. 链接问题:不同的编译单元可能会生成相同的模板实例化代码,导致链接器发现重复的定义。

为了解决这些问题,有两种常用方法:

1.显式实例化(Explicit Instantiation):
你可以在一个源文件中显式地为所需的所有模板实例化类型生成实例化定义。这样就可以避免在其他源文件中重复生成模板代码,只需要在其他源文件中声明这些实例化。这个方法可以减少编译时间并消除链接问题。

// my_template.h
template <class T>
void func(T param) { /* ... */ }// my_template.cpp
#include "my_template.h"
template void func<int>(int); // 显式实例化// main.cpp
extern template void func<int>(int); // 声明

2.模板定义在头文件中:
这种做法是最常见的,即将模板定义直接放在头文件中,由于头文件会被包含在每个使用它的源文件中,这样保证了编译器可以看到模板定义。虽然这种做法可能增加了编译时间,但在现代编译技术和硬件的支持下,它的影响已经大大减少了。


http://www.ppmy.cn/server/42490.html

相关文章

【数据结构】——时间复杂度与空间复杂度

时间复杂度与空间复杂度 数据结构算法算法效率时间复杂度大O的渐进表示法 空间复杂度常见复杂度对比 数据结构 数据结构是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种一种或者多种特定关系的数据元素的集合 数据结构就是内存中对数据进行管理 算法 算法就是定…

《Python编程从入门到实践》day37

# 昨日知识点回顾 制定规范、创建虚拟环境并激活&#xff0c;正在虚拟环境创建项目、数据库和应用程序 # 今日知识点学习 18.2.4 定义模型Entry # models.py from django.db import models# Create your models here. class Topic(models.Model):"""用户学习的…

GPT搜索引擎原型曝光!

OpenAI发布会前一天&#xff0c;员工集体发疯中……上演大型套娃行为艺术。 A&#xff1a;我为B的兴奋感到兴奋&#xff1b;B&#xff1a;我为C的兴奋感到兴奋……Z&#xff1a;我为这些升级感到兴奋 与此同时还有小动作不断&#xff0c;比如现在GPT-4的文字描述已不再是“最先…

关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

前情概要 这个问题其实困扰了我一周时间&#xff0c;一周都在 Google 上旅游&#xff0c;我要如何动态的设置 RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢&#xff1f;经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。 当然在此…

BGP联邦实验

实验要求及拓扑如下 1.子网划分 AS2基于172.16.0.0/16进行网段划分 划分出掩码为30的网段分给R2-R7作为PC&#xff0c;划分出掩码为24的网段作为AS2内部路由器之间的链路网段 172.16.0.0/30 172.16.0.4/30 172.16.0.8/30 172.16.0.12/30 172.16.0.16/30 …

楼道堆积物视觉识别监控系统

楼道堆积物视觉识别监控系统采用了AI神经网络和深度学习算法&#xff0c;楼道堆积物视觉识别监控系统通过摄像头实时监测楼道的情况&#xff0c;通过图像处理、物体识别和目标跟踪算法&#xff0c;系统能够精确地识别楼道通道是否被堆积物阻塞。楼道堆积物视觉识别监控系统检测…

生信机器学习入门1 - 数据预处理与线性回归(Linear regression)预测

数据预处理与线性回归&#xff08;Linear regression&#xff09;预测 数据集下载 # data文件夹中包含数据集文件 git https://github.com/LittleGlowRobot/machine_learning.git数据预处理主要步骤 参考github。 读取数据集 import numpy as np import pandas as pd####…

SpringMvc知识点(1)

执行流程 一、RequestMapping注解 这个注解既可以类上&#xff0c;也可以写在方法上。例如&#xff1a; RequestMapping(value "/user") Controller //UserHandler就是一个处理器/控制器,注入到容器 public class UserHandler {/*** 老韩解读* 1. methodRequestMe…