C++ 模板进阶:模板特化、全特化与偏特化详解

发布时间:2026/9/6 2:39:35
C++ 模板进阶:模板特化、全特化与偏特化详解 目录前言一前置知识非类型模板参数示例模拟固定大小数组相较于宏模版的优势二模板的特化模板的特化的概念函数模板特化类模板特化全特化偏特化半特化类模板特化应用示例三模板分离编译什么是分离编译模板的分离编译解决方法四模板总结结语前言C 模板最大的优势就是编写一套通用代码适配多种不同的数据类型。 但是通用逻辑并不能处理所有场景。举一个典型例子实现一个比较大小的模板函数普通对象可以正常比较如果传入指针类型通用模板只会比较指针地址而不是指针指向的内容业务逻辑就会出错。C 引入模板特化机制保留通用模板针对某些特殊类型单独实现一套定制逻辑。模板特化分为函数模板特化只有全特化不支持偏特化类模板特化分为全特化、偏特化半特化重要面试考点偏特化只存在类模板C 标准没有函数模板偏特化。一前置知识非类型模板参数模板参数分为两大类类型模板参数templateclass TT 代表一种数据类型。非类型模板参数传入编译期常量在模板内部可以将该参数作为常量使用。示例模拟固定大小数组#include iostream using namespace std; namespace wang { // N为非类型模板参数默认值为10 templateclass T, size_t N 10 class array { public: T operator[](size_t index){ return _array[index]; } const T operator[](size_t index)const{ return _array[index]; } size_t size()const{ return N; } bool empty()const{ return 0 N; } private: T _array[N]; }; }非类型模板参数限制1 浮点数、类对象、字符串字面量不允许作为非类型模板参数。2 非类型模板参数的值必须在编译期就可以确定不能是运行时变量。相较于宏模版的优势宏的数据是定死的但是这不是。宏的定义预处理阶段文本替换没有类型检查不属于 C 语法由预处理器处理。宏的代码#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; #define N 5//宏 class stack { private: int a[N]; int _top; }; int main() { stack s1; stack s2; return 0; }如上代码我定义了一个宏N为5那么N的大小就无法发生变化了实例化出来的对象中的a数组是定死了5个大小。模板的代码#define _CRT_SECURE_NO_WARNINGS #includeiostream using namespace std; templateclass T, size_t N 10 class Stack { private: int a[N]; int _top; }; int main() { Stackint, 5 st1; Stackint, 15 st2; Stackint st3; return 0; }通过上面的代码与下面的调试图可以看出非类型模板可以通过实例化进行扩容不会将空间大小固定死。适用的场景1.什么时候用宏头文件保护#ifndef HEADER #define HEADER条件编译#ifdef DEBUG调试开关。获取文件名、行号__FILE____LINE__业务逻辑尽量不要用宏实现函数。2.什么时候用模板写一套通用代码兼容多种数据类型泛型。通用容器、通用算法如 std::vector、std::sort。需要类型安全、可以调试、支持特化的泛型逻辑。一句话总结宏 文本粘贴预处理无类型不安全。模板 C 泛型工具编译期生成代码带类型检查安全强大。二模板的特化模板的特化的概念通常情况下使用模板可以实现一些与类型无关的代码但对于一些特殊类型的可能会得到一些错误的结果需要特殊处理。templateclass T bool Less(T left, T right) { return left right; }我们使用上面这个模板进行比较Date日期类的大小比较如下int main() { cout Less(1, 2) endl; // 可以比较结果正确 Date d1(2022, 7, 7); Date d2(2022, 7, 8); cout Less(d1, d2) endl; // 可以比较结果正确 Date* p1 d1; Date* p2 d2; cout Less(p1, p2) endl; // 可以比较结果错误 return 0; }为什么直接比数值与调用重载 比较都没有问题但是用用指针比较日期就出问题了这是为什么因为默认规则 用指针比较日期就是直接比指针地址并不是比较日期所以这个比较是不准确的。此时就需要对模板进行特化。即在原模板类的基础上针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。函数模板特化函数模板的特化步骤1. 必须要先有一个基础的函数模板。2. 关键字template后面接一对空的尖括号。3. 函数名后跟一对尖括号尖括号中指定需要特化的类型。4. 函数形参表: 必须要和模板函数的基础参数类型完全相同如果不同编译器可能会报一些奇怪的错误。这时我们可以使用函数模板的特化解决上面的问题了代码如下template bool LessDate*(Date* left, Date* right) { return *left *right; }使用函数模板的特化问题就解决了。为什么不会调用普通的模板而是调用模板的特化呢因为会优先调用最匹配的模板如果有对应的函数将不会调用模板而是直接调用函数。注意一般情况下如果函数模板遇到不能处理或者处理有误的类型为了实现简单通常都是将该函数直接给出。函数模板模板特化和函数重载是可以的一起存在的优先顺序是函数重载模板特化函数模板因为函数重载和模板特化同时存在的时候模板特化永远不会被调用这也是C不推荐函数模板特化的原因因为很容易就被重载覆盖了templateclass T bool Less(const T left,const T right) { return left right; } template bool Lessint*(const Date* left, const Date* right) { return *left *right; }对于const类型的模板的特化可以如上代码一样写吗是不行的。如上图这不是函数模板的特化这是为什么呢因为模板的const是修饰的left与right而模板的特化的const位与*的左边所以修饰的是left与right所指向的对象而不是left与right本身所以报错了。修改如下templateclass T bool Less(const T left,const T right) { return left right; } template bool Lessint*( Date* const left, Date* const right) { return *left *right; }类模板特化当我们有一个通用的类模板对于某一种 / 某一部分类型不想使用通用版本的逻辑想要单独写一份特殊实现这个过程就叫做模板特化。核心目的为特定类型定制不一样的代码逻辑。类模板特化分为两大类全特化完全特化将模板所有的模板参数全部指定为具体类型。偏特化半特化 / 部分特化只指定一部分模板参数剩下参数依旧保留可变。全特化全特化即是将模板参数列表中所有的参数都确定化。#include iostream using namespace std; // 通用类模板 templatetypename T class Test { public: void show() { cout 通用模板版本 endl; } }; // 全特化专门针对 string 类型 template class Teststring { public: void show() { cout string 全特化版本 endl; } }; int main() { Testint t1; t1.show(); // 通用模板版本 Teststring t2; t2.show(); // string 全特化版本 return 0; }偏特化半特化偏特化任何针对模版参数进一步进行条件限制设计的特化版本。偏特化有两种常见形式部分参数指定多个模板参数固定其中一部分范围限定不指定具体类型但是给模板参数增加限制例如特化所有指针类型全特化如下template struct LessDate* { bool operator()(Date* x, Date* y) const { return *x *y; } };半特化如下templateclass T struct LessT* { bool operator()(T* x, T* y) const { return *x *y; } };类模板特化应用示例#include iostream using namespace std; templatetypename T class PrintUtil { public: void print(const T val) { cout 值为 val endl; } }; // bool 全特化 template class PrintUtilbool { public: void print(const bool val) { if(val) cout 布尔值真 endl; else cout 布尔值假 endl; } }; int main() { PrintUtilint p1; p1.print(66); PrintUtilbool p2; p2.print(true); return 0; }三模板分离编译什么是分离编译一个程序项目由若干个源文件共同实现而每个源文件单独编译生成目标文件最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。模板的分离编译问题现象如果你像普通函数一样模板声明放.h模板实现放.cpp编译能通过链接报错undefined reference找不到函数定义。底层原理C 模板是延迟实例化 模板本身不是真正的函数 / 类。只有当代码使用了模板指定了类型编译器才会生成一份对应类型的实体代码。编译.cpp模板实现文件时编译器看不到外面哪里会实例化这个模板不会生成任何函数实体。编译main.cpp调用模板时只能看到.h里面的声明看不到实现代码也无法生成实例。两边都没生成实例最后链接就找不到实现。解决方法解决上述模板分离编译的问题有两种方法 将声明和定义放到一个文件中推荐用这种模板定义的位置显式实例化。不推荐不实用四模板总结【优点】1. 模板复用了代码节省资源更快的迭代开发C的标准模板库(STL)因此而产生。2. 增强了代码的灵活性。【缺陷】1. 模板会导致代码膨胀问题也会导致编译时间变长。2. 出现模板编译错误时错误信息非常凌乱不易定位错误。结语感谢你们的观看希望可以帮助到你们