1. 深入理解C++模板的非类型参数
1.1 非类型参数的本质与语法
在C++模板编程中,我们通常使用类型参数(如typename T)来创建通用代码。但很多人不知道的是,模板参数还可以是具体的值——这就是非类型参数。它的基本语法是在模板声明中指定一个具体类型的参数:
cpp复制template <typename T, int N> // T是类型参数,N是非类型参数
class FixedArray {
T elements[N]; // 使用非类型参数定义数组大小
};
这种设计允许我们在编译期就确定某些值,从而实现更高效的代码。比如上面的FixedArray,编译器知道数组的具体大小后,可以进行更好的优化。
注意:非类型参数的值必须在编译期就能确定,这意味着它不能是运行时才能知道值的变量。
1.2 允许使用的非类型参数类型
C++标准对非类型参数的类型有严格限制,主要包括以下几类:
-
整型及其衍生类型:
- 基本整型:
int,char,short,long等 - 无符号整型:
unsigned int,size_t等 - 枚举类型
- 基本整型:
-
指针类型:
- 对象指针:
int*,MyClass*等 - 函数指针
- 成员指针
- 对象指针:
-
引用类型:
- 左值引用:
int&,const std::string&等
- 左值引用:
-
特殊类型:
std::nullptr_t- C++20起支持浮点类型作为非类型参数
1.3 非类型参数的典型应用场景
1.3.1 固定大小容器
最常见的应用就是创建固定大小的容器,比如我们之前看到的FixedArray。标准库中的std::array也是类似的实现:
cpp复制template <typename T, std::size_t N>
struct array {
T _M_elems[N];
// ... 其他成员函数
};
这种设计比动态数组更高效,因为:
- 内存分配在栈上,没有堆分配开销
- 大小在编译期已知,编译器可以做更好的优化
- 不需要存储大小信息,节省内存
1.3.2 编译期计算
非类型参数可以用于编译期计算,这是模板元编程的基础。例如计算阶乘:
cpp复制template <int N>
struct Factorial {
static const int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
// 使用
constexpr int fact5 = Factorial<5>::value; // 120
1.3.3 策略模式
非类型参数可以用来实现策略模式,比如指定算法策略:
cpp复制template <typename T, typename Comparator = std::less<T>>
class SortedCollection {
// 使用Comparator比较元素
};
1.4 非类型参数的限制与注意事项
-
值必须是编译期常量:
cpp复制constexpr int size = 10; FixedArray<int, size> arr1; // 正确 int s = 10; FixedArray<int, s> arr2; // 错误!s不是编译期常量 -
某些类型不允许作为非类型参数:
- 浮点类型(C++20前)
- 类类型
- 字符
