
非类型模板参数
一、基础语法
1. 定义
cpptemplate <非类型参数类型 参数名>
- 类型模板参数:传递的是类型(
int/string/ 自定义类)- 非类型模板参数:传递的是常量值(编译期确定的数值、指针、引用等)
2. 支持的参数类型(C++17/20 标准)
- 整数类型(
int/bool/char/size_t等,最常用)- 指针 / 左值引用(指向全局 / 静态对象)
std::nullptr_t- 枚举类型
- C++20 新增:浮点类型、字面类型类对象
二、使用范例
cppnamespace bite { // 定义一个模板类型的静态数组 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 0 == _size;} private: T _array[N]; size_t _size; }; }
cpp越界读写都可以查出来, 因为会经过运算符重载的检查机制 array<int, 10> a1; 越界写 抽查结束邻近位置,远了不会报错 越界读 查不了 int a1[10]; a1[0] = 10; a1[9] = 100; cout << a1[10] << endl;
三、核心规则
必须是编译期常量非类型模板参数的值,必须在编译时就确定,不能是变量:
cppint n = 10; FixedArray<int, n> arr; // 错误!n 是运行时变量 FixedArray<int, 10> arr; // 正确!10 是编译期常量不同值 = 不同类型
FixedArray<int, 5>和FixedArray<int, 10>是完全不同的两个类,不能互相赋值。常用
size_t代替 int表示大小 / 长度时,用无符号类型size_t更规范:
cpptemplate <typename T, size_t size>
模板的特化
一、模板特化的核心概念
1.1 什么是模板特化
模板特化(Template Specialization)是 C++ 泛型编程的核心机制,允许为模板的特定参数类型提供定制化实现,编译器实例化时会优先选择特化版本而非通用模板。简单说:通用模板解决 "所有类型" 的通用逻辑,特化解决 "特定类型 / 类型模式" 的专属逻辑,实现 "通用 + 定制" 的灵活设计。
1.2 特化的两大核心类型
- 全特化(Full Specialization):为模板所有参数指定具体类型,生成完全独立的实现,匹配 "单一精确类型组合"。
- 偏特化(Partial Specialization):仅固定部分参数或约束参数特性(如指针、引用),匹配 "一类类型模式",仅支持类模板。
1.3 为什么要用模板特化
- 性能优化:为特定类型(如
bool、指针)提供更高效的实现(如std::vector<bool>的位压缩存储)。- 特殊类型适配:处理通用模板无法兼容的类型(如原生指针、引用、数组)。
- 类型特征实现:STL 中
std::is_pointer、std::is_const等类型判断工具底层依赖偏特化。- 代码灵活性:保留通用模板的复用性,同时为特殊场景提供定制逻辑,避免重复代码。
二、类模板全特化:精确匹配的专属实现
2.1 语法规则
- 必须在通用模板定义之后声明(编译器需先看到通用模板)。
- 特化声明前加
template<>(空模板参数列表,表示所有参数已具体化)。- 类名后用
<>指定所有模板参数的具体类型。- 全特化类是独立的类,可拥有与通用模板完全不同的成员(属性、方法)。
2.2 基础示例
cpp#include <iostream> #include <string> // 通用类模板:处理所有类型 template <typename T> class DataProcessor { public: void process(const T& data) { std::cout << "通用处理:" << data << std::endl; } }; // 全特化:针对int类型的专属实现 template <> class DataProcessor<int> { public: void process(int data) { std::cout << "int专属处理(平方):" << data * data << std::endl; } }; // 全特化:针对std::string类型的专属实现 template <> class DataProcessor<std::string> { public: void process(const std::string& data) { std::cout << "string专属处理(大写):" << char(toupper(data[0])) << data.substr(1) << std::endl; } }; // 通用模板:两个类型参数 template <typename T1, typename T2> class Pair { public: void print() { std::cout << "通用Pair:T1、T2" << std::endl; } }; // 全特化:T1=int,T2=double template <> class Pair<int, double> { public: void print() { std::cout << "全特化Pair:int + double" << std::endl; } };
三、类模板偏特化:模式匹配的灵活定制
偏特化是 C++ 特化的 "精髓",仅支持类模板(函数模板无偏特化,可用重载模拟),核心是 "固定部分参数" 或 "约束参数特性",匹配一类类型而非单一类型。
3.1 语法规则
- 声明前加
template<>,内部填写未被固定的模板参数。- 类名后
<>中,固定参数填具体类型,未固定参数填模板参数名。- 偏特化是 "模式匹配",只要实例化类型符合模式,就优先匹配偏特化。
3.2 偏特化的三大场景
场景 1:部分参数固定(最常用)
固定多参数模板中的部分参数,剩余参数保持泛型。
cpp// 通用模板:两个参数T1、T2 template <class T1, class T2> class Pair { public: void print() { std::cout << "通用:T1=" << typeid(T1).name() << ",T2=" << typeid(T2).name() << std::endl; } }; // 偏特化1:固定T2为int,T1保持泛型 template <class T1> class Pair<T1, int> { public: void print() { std::cout << "偏特化:T1泛型,T2=int" << std::endl; } }; // 偏特化2:固定T1为char,T2保持泛型 template <class T2> class Pair<char, T2> { public: void print() { std::cout << "偏特化:T1=char,T2泛型" << std::endl; } }; int main() { Pair<float, double> p1; p1.print(); // 通用:T1=float,T2=double Pair<std::string, int> p2; p2.print(); // 偏特化:T1泛型,T2=int Pair<char, long> p3; p3.print(); // 偏特化:T1=char,T2泛型 return 0; }场景 2:约束参数特性(指针 / 引用 /const)
匹配 "指针类型""引用类型""const 类型" 等模式,是实现类型特征的核心方式。
cpp// 通用模板 template <typename T> class TypeChecker { public: static void check() { std::cout << "通用类型" << std::endl; } }; // 偏特化:匹配所有指针类型(T*) template <typename T> class TypeChecker<T*> { public: static void check() { std::cout << "指针类型" << std::endl; } }; // 偏特化:匹配所有const类型(const T) template <typename T> class TypeChecker<const T> { public: static void check() { std::cout << "const类型" << std::endl; } }; // 偏特化:匹配所有引用类型(T&) template <typename T> class TypeChecker<T&> { public: static void check() { std::cout << "引用类型" << std::endl; } }; int main() { TypeChecker<int>::check(); // 通用类型 TypeChecker<int*>::check(); // 指针类型 TypeChecker<const int>::check(); // const类型 TypeChecker<int&>::check(); // 引用类型 return 0; }场景 3:非类型参数偏特化
针对模板的非类型参数(如
int N、bool B)进行特化,仅支持常量表达式
cpp// 通用模板:非类型参数N(size_t类型) template <size_t N> class Array { public: void size() { std::cout << "通用数组:大小=" << N << std::endl; } }; // 偏特化:N=0的特殊处理 template <> class Array<0> { public: void size() { std::cout << "空数组:大小为0" << std::endl; } }; // 偏特化:N=10的特殊处理 template <> class Array<10> { public: void size() { std::cout << "固定大小数组:大小=10" << std::endl; } }; int main() { Array<5> arr1; arr1.size(); // 通用数组:大小=5 Array<0> arr2; arr2.size(); // 空数组:大小为0 Array<10> arr3; arr3.size(); // 固定大小数组:大小=10 return 0; }3.3 偏特化的匹配优先级
当多个偏特化可匹配同一类型时,编译器选择最 "特殊" 的版本,规则:
- 精确匹配优先:全特化 > 偏特化 > 通用模板。
- 约束更严格优先:例如
Pair<int, int>优先匹配 "T1=int 且 T2=int" 的偏特化,而非仅固定 T1=int 的偏特化。
四、函数模板特化:全特化可用,偏特化禁用
4.1 函数模板全特化
语法与类模板全特化类似,但实践中优先用重载替代(特化不参与重载决议,易引发匹配异常)。
cpp// 通用函数模板 template <typename T> void print(const T& data) { std::cout << "通用打印:" << data << std::endl; } // 函数模板全特化:针对int template <> void print<int>(const int& data) { std::cout << "int专属打印:" << data << std::endl; } // 函数模板全特化:针对std::string template <> void print<std::string>(const std::string& data) { std::cout << "string专属打印:" << data << std::endl; } int main() { print(3.14); // 通用打印:3.14 print(10); // int专属打印:10 print("C++模板"); // string专属打印:C++模板 return 0; }4.2 函数模板无偏特化(重点)
C++ 标准禁止函数模板偏特化,以下代码编译报错:
cpp// 通用函数模板 template <typename T1, typename T2> void func(T1 a, T2 b); // 错误:函数模板不支持偏特化 template <typename T1> void func<T1, int>(T1 a, int b);4.3 替代方案:函数重载
用函数重载模拟偏特化效果,更安全、更符合 C++ 规范:
cpp#include <iostream> // 通用模板 template <typename T1, typename T2> void func(T1 a, T2 b) { std::cout << "通用:" << a << "、" << b << std::endl; } // 重载:固定T2=int,模拟偏特化 template <typename T1> void func(T1 a, int b) { std::cout << "重载(模拟偏特化):" << a << "、int=" << b << std::endl; } int main() { func(10, 3.14); // 通用:10、3.14 func("hello", 20); // 重载(模拟偏特化):hello、int=20 return 0; }
模板分离编译
一、什么是「模板分离编译」
常规非模板类 / 函数的标准写法(分离编译):
cpp// test.h 声明 void func(int a); // test.cpp 实现 #include "test.h" void func(int a) { /* ... */ }模板的错误写法(直接照搬):
cpp// demo.h 模板声明 template<typename T> T add(T a, T b); // demo.cpp 模板实现 ❌ 分离编译会链接失败 #include "demo.h" template<typename T> T add(T a, T b) { return a + b; }
二、核心原理:为什么模板不能直接分离编译?
1. 模板 = 蓝图,不是真实代码
编译器看到模板本身,不会生成任何二进制代码。只有当 模板被使用(实例化) 时,才会生成对应类型的函数 / 类。
cppadd<int>(1,2); // 这里才会生成 int add(int,int)2. 编译模型:翻译单元独立编译
C/C++ 编译是单个文件独立编译:
main.cpp包含.h,只看到声明,看不到.cpp里的实现demo.cpp里有模板实现,但没有被调用,编译器不生成任何实例代码- 链接阶段:找不到
add<int>实现 → 报未定义错误一句话总结:模板实现必须在实例化时可见,否则编译器无法生成代码。
阶段 输入 输出 核心任务 报错类型 预处理 .c/.cpp .i 处理 #include、#define、删注释 头文件找不到、宏错误 编译 .i .s 语法分析 → 生成汇编 语法错、类型错、未定义标识符 汇编 .s .o 汇编 → 机器码 无(汇编指令错) 链接 .o+.a 可执行文件 合并文件、解析符号 未定义函数、重复定义、找不到库
三、3 种标准解决方案(企业级常用)
方案 1:包含模式(最简单、最常用)
把模板实现直接写在头文件里,或者在头文件末尾
#include实现文件。写法 A:实现全放 .h(推荐)
cpp// demo.h #pragma once template<typename T> T add(T a, T b) { return a + b; }写法 B:.h + .inl/.tpp 分离(美观、工程规范)
cpp// demo.h 声明 #pragma once template<typename T> T add(T a, T b); #include "demo.inl" // 关键!末尾包含实现
cpp// demo.inl 实现(不是 cpp!) #pragma once template<typename T> T add(T a, T b) { return a + b; }✅ 优点:简单、无链接错误、跨平台
✅ 企业主流方案(STL / Boost 都这么做)
方案 2:显式实例化(控制代码膨胀)
如果你明确知道模板会用哪些类型,可以在
.cpp里手动实例化。
cpp// demo.h template<typename T> T add(T a, T b);
cpp// demo.cpp #include "demo.h" template<typename T> T add(T a, T b) { return a + b; } // 显式实例化 template int add<int>(int, int); template double add<double>(double, double);✅ 优点:实现真正分离、减少目标文件体积
❌ 缺点:不灵活,新增类型必须修改实现文件
适用场景:模板只支持固定类型(如仅 int/double)。
方案 3:export 关键字(废弃,不要用)
C++98 曾用
export支持模板分离编译,但所有编译器都放弃支持。C++17 直接移除该特性。
四、企业级最佳实践(直接照抄)
通用规范
- 模板声明 + 实现都放在头文件
- 实现太长 → 拆成
.h+.inl(inline)- 头文件用
#pragma once或#ifndef防止重复包含- 不要把模板实现放在
.cpp标准目录结构
cppdemo/ ├─ demo.h // 声明 └─ demo.inl // 实现类模板示例(最实用)
cpp// vector.h #pragma once template<typename T> class Vector { public: void push_back(const T& val); private: T* data; }; #include "vector.inl"
cpp// vector.inl #pragma once template<typename T> void Vector<T>::push_back(const T& val) { // 实现 }

