跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

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

C++ 模板进阶涉及非类型模板参数、模板特化及分离编译机制。非类型参数仅限整型等常量,不支持浮点或类对象。模板特化分为函数模板特化和类模板特化,后者包含全特化与偏特化,用于处理特殊类型场景如指针比较。模板分离编译需将声明与定义置于同一头文件或进行显式实例化。模板复用代码但可能导致膨胀和编译错误难定位。

观心发布于 2026/3/30更新于 2026/6/116 浏览
C++ 模板进阶:非类型参数、特化与分离编译

1. 非类型模板参数

模板参数分为类型形参与非类型形参。

类型形参即:出现在模板参数列表中,跟在 class 或 typename 之类的后面的参数类型名称。

非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

namespace Achieve {
    // 定义一个模板类型的静态数组
    template<class 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 _size; }
        bool empty() const { return _size == 0; }
    private:
        T _array[N];
        size_t _size;
    };
}

注意:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。(只有整形可以,像 int、long、无符号整形、bool 等)。(C++20 支持 double)
  2. 非类型的模板参数必须在编译期就能确认结果。

2. 模板的特化

2.1 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能回得到一些错误的结果,需要特殊处理。如:实现了一个专门用来进行小于比较的函数模板。

// 函数模板——参数匹配
template<class T> bool Less(T left, T right) {
    return left < right;
}
int main() {
    cout << Less(1, 2) << endl; // 可以比较,结果正确
    Date d1(2022, 4, 12);
    Date d2(2003, 6, 20);
    cout << Less(d1, d2) << endl; // 可以比较,结果正确
    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl; // 可以比较,结果错误
    return 0;
}

可以看到,Less 绝对多数情况下都可以正常比较,但是在特殊场景下得到错误的结果。上述示例中,p1 指向的 d1 显然大于 p2 指向的 d2 对象,但是 Less 内部并没有比较 p1 和 p2 指向的对象内容,而比较的是 p1 和 p2 指针的地址,这就无法达到预期而错误。

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特殊化中分为函数模板特化与类模板特化。

2.2 函数模板特化

函数模板的特化步骤:

  1. 必须先有一个基础的函数模板
  2. 关键字 template 后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表:必须要和模板函数的基础参数类型完全相同,如果编译器不同可能会报一些奇怪的错误。
// 函数模板———参数匹配
template<class T> bool Less(T left, T right) {
    return left < right;
}

// 特化版本
template<> bool Less<Date*>(Date* left, Date* right) {
    return *left < *right;
}

int main() {
    cout << Less(1, 2) << endl;
    Date d1(2022, 4, 12);
    Date d2(2003, 6, 20);
    cout << Less(d1, d2) << endl;
    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
    return 0;
}

注意:一般情况下若函数模板遇到不能处理或者有误的类型,为了实现简单通常都是将该函数直接给出。

bool Less(Date* left, Date* right) {
    return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化是特别给出,因此函数模板不建议特化。

2.3 类模板特化

2.3.1 全特化

全特化即是将模板参数列表中所有的参数都确定化。

template<class T1, class T2> class Data {
public:
    Data() { cout << "Data<T1,T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

// 全特化
template<> class Data<int, double> {
public:
    Data() { cout << "Data<int,double>" << endl; }
private:
    int _d1;
    double _d2;
};

void Test() {
    Data<int, int> d1;
    Data<int, double> d2;
}
2.3.2 偏特化

偏特化:任何针对模板参数进一步条件限制设计的特化版本。比如对于以下模板类:

template<class T1, class T2> class Data {
public:
    Data() { cout << "Data<T1,T2>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

偏特化有以下两种表现方式:

  • 部分特化:将模板参数类表中的一部分参数特化。
// 将第二个参数特化为 int
template<class T1> class Data<T1, int> {
public:
    Data() { cout << "Data<T1,int>" << endl; }
private:
    T1 _d1;
    int _d2;
};
  • 参数更进一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
// 两个参数偏特化为指针类型
template<class T1, class T2> class Data<T1*, T2*> {
public:
    Data() { cout << "Data<T1*,T2*>" << endl; }
private:
    T1 _d1;
    T2 _d2;
};

// 两个参数偏特化为引用类型
template<class T1, class T2> class Data<T1&, T2&> {
public:
    Data(const T1& d1, const T2& d2) :_d1(d1), _d2(d2) {
        cout << "Data<T1&,T2&>" << endl;
    }
private:
    const T1& _d1;
    const T2& _d2;
};

void test2() {
    Data<double, int> d1;      // 调用特化的 int 版本
    Data<int, double> d2;      // 调用基础的模板
    Data<int*, int*> d3;       // 调用特化的指针版本
    Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}
2.3.3 类模板特化应用示例

有如下专门用来按照小于比较的类模板 Less:

template<class T> struct Less {
    bool operator()(const T& x, const T& y) const {
        return x < y;
    }
};

int main() {
    Date d1(2022, 10, 5);
    Date d2(2024, 11, 3);
    Date d3(2021, 1, 4);
    vector<Date> v1;
    v1.push_back(d1);
    v1.push_back(d2);
    v1.push_back(d3);
    
    // 可以直接排序,结果是日期升序
    sort(v1.begin(), v1.end(), Less<Date>());
    
    vector<Date*> v2;
    v2.push_back(&d1);
    v2.push_back(&d2);
    v2.push_back(&d3);
    
    // 可以直接排序,结果错误日期还是不升序,而 v2 放的地址是升序
    // 此处需要在排序过程中,让 sort 比较 v2 中存放地址指向的日期对象
    // 但是走 Less 模板,sort 在排序时实际比较的是 v2 中指针的地址,因此无法达到预期
    sort(v2.begin(), v2.end(), Less<Date*>());
    return 0;
}

通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是若待排序元素是指针,结果就不一定正确。因为 sort 最终按照 Less 模板中方式比较,所以只会比较指针,而不是比较指针指向空间中的内容,此时可以使用类版本特化来处理上述问题:

// 对 Less 类模板按照指针方式特化
template<> struct Less<Date*> {
    bool operator()(Date* x, Date* y) const {
        return *x < *y;
    }
};

特化之后,在运行上述代码,就可以得到正确的结果。

3. 模板分离编译

3.1 什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.2 模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

// a.h
template<class T> T Add(const T& left, const T& right);

// a.cpp
template<class T> T Add(const T& left, const T& right) {
    return left + right;
}

// main.cpp
#include "a.h"
int main() {
    Add(1, 2);
    Add(1.0, 2.0);
    return 0;
}

分析:

由于模板实例化发生在编译阶段,当 main.cpp 包含 a.h 时,编译器只看到了声明,没有看到定义,因此无法生成 Add 函数的实例化代码,导致链接错误。

3.3 解决方法

  1. 将声明和定义放到一个文件 "xxx.hpp" 里面或 "xxx.h" 里面。(推荐)
  2. 模板定义的位置显示实例化。(不推荐)

4. 模板总结

【优点】

  1. 模板复用了代码,节省资源,更快的迭代开发,C++ 的标准模板库(STL)因此产生
  2. 增强了代码的灵活性

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

目录

  1. 1. 非类型模板参数
  2. 2. 模板的特化
  3. 2.1 概念
  4. 2.2 函数模板特化
  5. 2.3 类模板特化
  6. 2.3.1 全特化
  7. 2.3.2 偏特化
  8. 2.3.3 类模板特化应用示例
  9. 3. 模板分离编译
  10. 3.1 什么是分离编译
  11. 3.2 模板的分离编译
  12. 3.3 解决方法
  13. 4. 模板总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 辅助编程:如何利用 GitHub Copilot 等工具提升开发效率
  • Python 驱动 Ksycopg2 连接和使用 Kingbase 数据库实战
  • IntelliJ IDEA 中修改 Git 用户名、邮箱及切换账号
  • 2025 信奥赛 C++ 提高组 CSP-S 复赛真题及题解:员工招聘
  • WebAssembly 跨平台优化:FFmpeg.wasm 架构解析与性能提升
  • PHP 核心基础知识点梳理(二)
  • C++ vector 容器详解(一)
  • Java synchronized 底层原理:字节码、对象头与锁升级
  • 滑动窗口算法:长度最小的子数组
  • Copilot Pro 使用指南:模型配额与选型策略
  • llama.cpp 与 llama-server 安装部署验证
  • Vue 3 前端调试与开发指南
  • PingFang SC Regular 字体资源与使用说明
  • 前端如何实现用户回到上次阅读的位置
  • Seedance 2.0 双分支扩散变换器架构解析与工程实现
  • Android 开发中 OOM 问题的常见原因与解决方案
  • 银发族 AI 助手:AIGC 陪聊、防骗与解闷实战方案
  • ClawX:OpenClaw 可视化桌面客户端实战指南
  • 数据结构:带头双向循环链表的定义与实现
  • CentOS 7 Firewalld 基础:端口开放与 IP 白名单配置

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online