C++之模板进阶
模板进阶
- 非类型模板参数
- 一、非类型模板参数的语法
- 二、非类型模板参数的类型
- 三、非类型模板参数的优点
- 四、限制
- 模板特化
- 一、模板特化的分类
- 二、函数模板的特化
- 全特化语法
- 三、类模板的特化
- 1. 全特化
- 全特化语法
- 2. 偏特化
- 偏特化语法
- 四、模板特化的匹配规则
- 模板分离编译
- 一、模板分离编译的背景
- 二、模板分离编译的问题
- 三、解决模板分离编译问题的方法
- 1. **显式实例化(Explicit Instantiation)**
- 语法
- 2. **模板定义与声明分离**
- 3. **使用内联模板库(Inline Template Library)**
- 四、总结
- 模板总结

非类型模板参数
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
一、非类型模板参数的语法
非类型模板参数的声明方式如下:
template <typename T, int N>
class ClassName {// 类成员
};
其中,int N
是一个非类型模板参数,表示一个整数。
二、非类型模板参数的类型
非类型模板参数可以是以下类型:
- 整数类型(如
int
、long
、unsigned int
等) - 枚举类型
- 指针类型
- 引用类型
- 布尔类型
但需要注意的是,非类型模板参数的值必须在编译时是已知的常量(即编译时常量表达式)。
三、非类型模板参数的优点
类型安全
- 模板参数的类型检查在编译时完成,避免了运行时错误。
四、限制
- 编译时常量
- 非类型模板参数的值必须是编译时已知的常量,不能是变量。
- 类型限制
- 非类型模板参数的类型不能是浮点类型(如
float
、double
),但可以是整数类型或指针类型。
- 非类型模板参数的类型不能是浮点类型(如
模板特化
模板特化(Template Specialization)是C++模板机制中一个非常重要的特性。它允许程序员为特定的模板参数提供专门的实现,而不是使用模板的一般定义。模板特化可以用于函数模板和类模板,用于处理某些特殊类型或参数时提供更优化或更合适的实现。
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
一、模板特化的分类
模板特化分为两种:
- 全特化(Full Specialization)
- 为模板的所有参数提供具体的类型或值,从而提供一个完全不同的实现。
- 偏特化(Partial Specialization)
- 仅对模板的部分参数进行特化,而其他参数保持通用。
二、函数模板的特化
函数模板的特化通常只能是全特化,因为函数模板的参数不能部分指定。
全特化语法
template <>
返回类型 函数名(参数列表) {// 特化版本的实现
}
函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
三、类模板的特化
类模板的特化可以是全特化,也可以是偏特化。
1. 全特化
全特化为模板的所有参数提供具体的类型或值,从而提供一个完全不同的实现。
全特化语法
template <>
class 类名<特化参数列表> {// 特化版本的成员
};
2. 偏特化
偏特化是指仅对模板的部分参数进行特化,而其他参数保持通用。偏特化只能用于类模板,不能用于函数模板。
偏特化语法
template <typename T1, typename T2>
class 类名<T1, 特化参数> {// 偏特化版本的成员
};
偏特化有以下两种表现方式:
- 部分特化
将模板参数类表中的一部分参数特化。 - 参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设出来的一个特化版本。
四、模板特化的匹配规则
- 优先级
- 特化版本(全特化或偏特化)的优先级高于一般模板。
- 如果存在多个特化版本,编译器会选择最匹配的特化版本。
- 冲突
- 如果多个特化版本之间存在冲突,编译器会报错。
- 例如,不能同时定义两个全特化版本
xxx<int>
和xxx<int>
,也不能定义两个冲突的偏特化版本。
模板分离编译
什么是分离编译 :
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
一、模板分离编译的背景
在C++中,模板的定义和实例化通常是分开的:
- 模板定义:模板的通用代码部分,通常放在头文件(
.h
或.hpp
)中。 - 模板实例化:在具体的使用场景中,根据模板参数生成具体的代码。
由于模板的实例化需要知道模板的具体参数类型,因此模板的定义必须在编译时对所有可能的实例化可见。这导致模板代码通常不能像普通函数或类那样直接分离编译。
二、模板分离编译的问题
-
编译时间增加
- 每次使用模板时,编译器都需要根据具体的模板参数生成代码,这可能导致编译时间显著增加。
- 如果模板定义在头文件中,每个包含该头文件的源文件(
.cpp
)都会重新实例化模板,导致重复编译。
-
代码膨胀
- 每个模板实例化都会生成一份独立的代码,可能导致最终生成的可执行文件体积增大。
-
难以分离编译
- 模板的定义和实例化通常需要在同一编译单元中完成,这使得模板代码难以像普通函数或类那样分离到单独的源文件中。
三、解决模板分离编译问题的方法
1. 显式实例化(Explicit Instantiation)
显式实例化是指在源文件中显式地指定模板的实例化类型。这样可以将模板的实例化代码集中到一个编译单元中,从而减少重复编译和代码膨胀。
语法
// 在源文件中显式实例化模板
template class TemplateName<SpecificType>;
优点:
- 减少重复编译,提高编译效率。
- 减少代码膨胀,优化最终生成的可执行文件大小。
缺点:
- 需要显式指定所有可能的实例化类型,不够灵活。
2. 模板定义与声明分离
将模板的定义和声明分开,定义放在源文件中,声明放在头文件中。这种方法在C++中通常不推荐,因为模板的定义必须对所有可能的实例化可见。
优点:
- 模板的定义可以隐藏在源文件中,减少头文件的复杂性。
缺点:
- 如果没有显式实例化,编译器可能无法生成所需的模板实例化代码,导致链接错误。
3. 使用内联模板库(Inline Template Library)
将模板的定义和声明全部放在头文件中,这样可以确保模板的定义对所有编译单元可见。这种方法是最常用的解决方案,但会导致编译时间增加和代码膨胀。
优点:
- 简单易用,不需要显式实例化。
缺点:
- 编译时间增加,代码膨胀。
四、总结
模板的分离编译是一个复杂的问题,主要原因是模板的实例化依赖于具体的类型参数。以下是几种解决模板分离编译问题的方法的总结:
- 显式实例化:通过显式指定模板的实例化类型,将模板代码集中到一个编译单元中,减少重复编译和代码膨胀。
- 模板定义与声明分离:将模板的定义放在源文件中,声明放在头文件中,但需要显式实例化,否则可能导致链接错误。
- 内联模板库:将模板的定义和声明全部放在头文件中,简单易用,但会导致编译时间增加和代码膨胀。
模板总结
优点
-
模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
-
增强了代码的灵活性
缺陷
-
模板会导致代码膨胀问题,也会导致编译时间变长
-
出现模板编译错误时,错误信息非常凌乱,不易定位错误