豆包
一、默认类模板参数
在 C++ 中,类模板参数可以设置默认值,这和函数参数的默认值类似。当实例化类模板时,如果用户没有提供对应的模板参数,就会使用默认参数。下面来详细了解相关内容:
基础语法
默认类模板参数的设置是在类模板定义时,通过在模板参数列表里使用=
来为类型参数赋予默认值。其语法格式如下:
template<typename T = 默认类型, ...>
class 类名 {
// 类的定义
};
实际应用
1. 容器类中的应用
下面是一个简单容器类的模板,它默认使用std::vector
作为底层存储结构:
#include <vector>
#include <list>
template<typename T, typename Container = std::vector<T>>
class MyContainer {
private:
Container elements;
public:
void add(const T& value) { elements.push_back(value); }
// 其他接口...
};
// 实例化时可以省略第二个参数
MyContainer<int> vecContainer; // 等价于 MyContainer<int, std::vector<int>>
2. 依赖之前模板参数的默认参数
默认参数能够引用之前声明的模板参数:
template<typename T, typename Allocator = std::allocator<T>>
class MyList { /* ... */ };
3. 非类型模板参数的默认值
非类型模板参数也能设置默认值:
template<typename T = int, size_t N = 100>
class FixedArray {
T data[N];
public:
// 类的实现
};
// 使用默认参数创建对象
FixedArray<> arr; // 等价于 FixedArray<int, 100>
相关规则
- 默认参数的位置:和函数默认参数一样,默认模板参数必须从右向左依次设置。也就是说,如果一个模板参数有默认值,那么它右边的所有参数都得有默认值。
- 显式指定参数:在实例化类模板时,用户可以通过显式指定参数来覆盖默认值。
- 与函数模板的差异:函数模板只有在 C++11 之后才支持默认参数,而类模板从 C++98 就开始支持了。
- 模板特化中的默认参数:在类模板的部分特化或者全特化版本中,不能重新定义默认参数。
示例代码
下面的代码展示了默认模板参数的使用方法:
#include <iostream>
#include <vector>
#include <list>
// 带有默认参数的类模板
template<typename T = int,
typename Container = std::vector<T>,
size_t MaxSize = 1000>
class Collection {
private:
Container items;
public:
void add(const T& item) {
if (items.size() < MaxSize)
items.push_back(item);
}
void print() const {
for (const auto& item : items)
std::cout << item << " ";
std::cout << std::endl;
}
};
int main() {
// 使用全部默认参数
Collection<> defaults;
defaults.add(10);
defaults.add(20);
defaults.print(); // 输出: 10 20
// 部分指定参数
Collection<double, std::list<double>> custom;
custom.add(3.14);
custom.add(2.718);
custom.print(); // 输出: 3.14 2.718
return 0;
}
总结
- 默认类模板参数为模板的使用提供了便利,让代码更加简洁。
- 它支持类型参数和非类型参数。
- 在实例化模板时,用户可以根据需要覆盖默认参数,也可以使用默认值。
二、类模板的非类型参数
在 C++ 中,类模板的非类型参数允许你将值(而非类型)作为模板参数传递。这为模板提供了在编译时的灵活性,使得类可以根据编译时常量定制其行为。下面详细介绍非类型模板参数的相关内容:
基本语法
非类型模板参数通过特定类型声明,而非typename
或class
关键字。语法格式如下:
template <类型 参数名>
class 类名 {
// 类定义
};
常见类型的非类型参数
1. 整数类型(包括枚举)
template <int N>
class Array {
private:
int data[N]; // 使用非类型参数指定数组大小
public:
int size() const { return N; }
};
// 实例化时传递整数值
Array<10> arr; // N = 10
2. 指针或引用类型
template <const char* str>
class Message {
public:
void print() { std::cout << str << std::endl; }
};
const char msg[] = "Hello, World!";
Message<msg> obj; // 传递指针参数
3. 成员指针类型
template <int ClassType::* member>
class MemberAccessor {
private:
ClassType obj;
public:
int get() const { return obj.*member; }
void set(int value) { obj.*member = value; }
};
class MyClass {
public:
int x;
};
MemberAccessor<&MyClass::x> accessor; // 传递成员指针
关键特性与限制
编译时常量要求
非类型参数必须是编译时常量表达式。例如:constexpr int SIZE = 10; Array<SIZE> valid; // 合法:SIZE是编译时常量 int runtime_size = 10; Array<runtime_size> invalid; // 非法:runtime_size是运行时变量
允许的类型限制
非类型参数的类型通常限于:- 整数或枚举类型
- 指针或引用(指向对象、函数或成员)
std::nullptr_t
(即nullptr
的类型)- 浮点数类型(C++20 起支持,但仅限
constexpr
值)
模板特化
非类型参数可用于模板特化:cpp
运行
template <int N> class Math { public: static constexpr int value = N * 2; }; template <> class Math<0> { // 全特化 public: static constexpr int value = 1; // 特殊值 };
参数值作为常量表达式
非类型参数的值在编译时已知,可用于静态断言、数组大小等:cpp
运行
template <int N> void static_assertion() { static_assert(N > 0, "N must be positive"); }
应用场景
1. 固定大小容器
template <typename T, size_t N>
class Stack {
private:
T data[N];
size_t top;
public:
Stack() : top(0) {}
bool empty() const { return top == 0; }
bool full() const { return top == N; }
// ...
};
2. 编译时计算
template <int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> { // 终止条件
static constexpr int value = 1;
};
// 使用:Factorial<5>::value 等于 120
3. 策略模式的编译时实现
template <typename T, void (*SortAlgorithm)(T*, size_t)>
class Sorter {
public:
void sort(T* arr, size_t size) {
SortAlgorithm(arr, size);
}
};
void bubble_sort(int* arr, size_t size); // 假设已实现
Sorter<int, bubble_sort> sorter; // 传递函数指针
注意事项
地址必须是外部链接
传递指针或引用时,对象必须具有外部链接(即全局变量或extern
变量):extern const char msg[] = "Global"; // 合法 Message<msg> valid; const char local_msg[] = "Local"; // 非法(无外部链接) Message<local_msg> invalid; // 错误!
浮点数参数(C++20+)
C++20 允许constexpr
浮点数作为非类型参数:template <double Value> struct PiApproximation { static constexpr double value = Value; }; PiApproximation<3.14159> pi;
避免代码膨胀
不同的非类型参数值会实例化不同的模板类,可能导致代码体积增大。
总结
非类型模板参数允许类模板在编译时接受常量值,从而实现:
- 固定大小容器(如静态数组)
- 编译时计算(如阶乘、斐波那契数列)
- 策略模式的编译时实现
- 参数化算法或配置
使用时需注意参数类型限制和编译时常量要求,以确保代码合法且高效。
通义千问
一、默认类模板参数
在C++中,默认类模板参数允许你在声明模板时为模板参数指定默认值。这使得在实例化模板时,如果某些模板参数没有被显式提供,它们将自动使用默认值。这一特性增强了模板的灵活性和重用性。
基本语法
定义带有默认参数的类模板的基本语法如下:
template <typename T1, typename T2 = int>
class MyClass {
// 类定义
};
在这个例子中,T2
有一个默认类型 int
。这意味着如果你在实例化 MyClass
时不提供第二个模板参数,它将默认使用 int
。
使用示例
假设你有如下定义的一个类模板:
template <typename T1, typename T2 = std::vector<T1>>
class Container {
public:
void add(const T1& element) {
elements.push_back(element);
}
void print() const {
for (const auto& elem : elements) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
private:
T2 elements;
};
在这个例子中,Container
的第二个模板参数 T2
默认是 std::vector<T1>
。你可以这样使用这个类模板:
Container<int> myIntContainer; // 使用默认的 T2 = std::vector<int>
Container<int, std::list<int>> myListContainer; // 显式指定 T2 为 std::list<int>
注意事项
- 默认模板参数只能用于类模板,不适用于函数模板(C++11之前)。不过,从C++11开始,函数模板也可以拥有默认模板参数。
- 如果一个模板参数有默认值,那么其后面的所有模板参数都必须有默认值或者在使用时提供实际参数。
- 在使用模板时,可以混合使用显式提供的模板参数和默认模板参数。
通过合理利用默认类模板参数,可以使你的代码更加简洁、灵活,同时也提高了代码的可维护性和复用性。
在C++中使用类模板时,如果提供了模板参数,就会使用你提供的;如果不提供,则会使用默认的参数。
✅ 简单总结:
- 提供参数 ➜ 使用你指定的类型。
- 不提供参数 ➜ 使用模板定义中设置的默认类型。
📌 示例说明
我们来看一个具体的例子:
#include <iostream>
#include <vector>
#include <list>
template <typename T = int, typename Container = std::vector<T>>
class MyContainer {
public:
void add(const T& value) {
data.push_back(value);
}
void print() const {
for (const auto& val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
}
private:
Container data;
};
这个类模板 MyContainer
有两个模板参数:
T
:元素类型,默认为int
Container
:容器类型,默认为std::vector<T>
🔧 实例化方式
情况一:不提供任何参数(全部使用默认值)
MyContainer<> c1; // 等价于 MyContainer<int, std::vector<int>>
c1.add(10);
c1.add(20);
c1.print(); // 输出: 10 20
情况二:只提供第一个参数(第二个使用默认值)
MyContainer<double> c2; // 等价于 MyContainer<double, std::vector<double>>
c2.add(3.14);
c2.add(2.71);
c2.print(); // 输出: 3.14 2.71
情况三:两个参数都提供
MyContainer<float, std::list<float>> c3;
c3.add(1.1f);
c3.add(2.2f);
c3.print(); // 输出: 1.1 2.2
情况四:提供第二个参数但不提供第一个(不行!必须从左到右顺序提供)
// 错误写法:不能跳过前面的参数
MyContainer<, std::list<int>> c4; // 编译错误 ❌
如果你想要自定义后面的参数,必须显式写出前面的所有参数。
⚠️ 注意事项
- 默认参数只能放在模板参数列表的最后面。
- 如果某个参数有默认值,它之后的所有参数也必须都有默认值或在使用时被显式提供。
- C++11 及以后支持函数模板也有默认模板参数,但类模板更常用。
二、非类型模板参数
非类型模板参数(Non-type template parameters, NTTPs)在C++中提供了一种强大的方式来增强模板的功能和灵活性。虽然确实有很多方式可以实现类似的功能,但非类型模板参数有其独特的优势和用途:
优化性能:非类型模板参数可以在编译期确定值,这意味着它们可以帮助生成高度优化的代码。例如,使用数组大小作为非类型模板参数可以让编译器在编译时就知道数组的确切大小,从而进行更有效的优化。
实例化不同的类或函数版本:通过非类型模板参数,可以根据不同的参数值实例化不同的类或函数版本。这允许程序根据需要灵活地选择不同的实现,而无需手动编写多个几乎相同的类或函数定义。
常量表达式支持:非类型模板参数必须是编译时常量表达式(如整数、指针等),这使得它们非常适合用于那些在编译时就可以确定并且不会改变的值。这种特性使得模板能够更好地利用编译时计算能力,提高运行效率。
增强模板的通用性:非类型模板参数使模板不仅仅限于处理不同类型的数据,还可以基于数值或其他编译时常量进行操作。这增加了模板的通用性和适用范围。
简化代码:在某些情况下,使用非类型模板参数可以使代码更加简洁和直观。比如,当你想要创建一个固定大小的数组或者指定某个特定的策略时,直接在模板参数列表中指定这些值通常比通过其他方式(如继承或设置成员变量)更加简单明了。
总之,尽管可以通过其他编程技巧实现相似的目标,非类型模板参数为C++开发者提供了一种强大且灵活的工具,特别适用于需要高性能、编译时决定行为以及希望增加代码复用性的场景。
在 C++ 中,类模板不仅可以接受类型参数(如 typename T
或 class T
),还可以接受非类型参数(non-type template parameters)。
🧩 什么是“非类型参数”?
非类型模板参数指的是:传递给模板的不是类型,而是具体的值。这些值必须是编译时常量表达式(constant expression)。
✅ 合法的非类型参数类型包括:
- 整型(int、char、bool、enum 等)
- 指针类型
- 引用类型
- std::nullptr_t
- 浮点数(从 C++20 开始支持)
🔧 基本语法示例
template<typename T, int N>
class MyArray {
private:
T data[N]; // 使用非类型参数作为数组大小
public:
T& operator[](int i) { return data[i]; }
const T& operator[](int i) const { return data[i]; }
static constexpr int size() { return N; }
};
✅ 使用方式:
MyArray<int, 10> arr; // T = int, N = 10
for (int i = 0; i < arr.size(); ++i)
arr[i] = i * 2;
📌 非类型参数的常见用途
1. 固定大小容器
template<typename T, size_t Size>
class StaticVector {
T data[Size];
size_t count = 0;
public:
void push_back(const T& value) {
if (count < Size)
data[count++] = value;
}
// ...
};
2. 编译期配置选项
template<bool DebugMode = false>
class Logger {
public:
void log(const std::string& msg) {
if constexpr (DebugMode) {
std::cout << "[DEBUG] " << msg << std::endl;
} else {
std::cout << msg << std::endl;
}
}
};
Logger<true> debugLogger; // 输出带调试前缀
Logger<> releaseLogger; // 不带调试前缀
3. 接收函数指针或对象引用(高级用法)
template<int (&Func)(int)>
class Wrapper {
public:
int call(int x) {
return Func(x);
}
};
int square(int x) {
return x * x;
}
Wrapper<square> w;
int result = w.call(5); // 返回 25
⚠️ 注意事项
注意点 | 说明 |
---|---|
必须是常量表达式 | 非类型参数必须在编译时确定 |
不允许运行时变量 | 不能传入局部变量或运行时计算的结果 |
C++20 才支持浮点数 | 如:template<double D> 是 C++20 起才合法 |
类型匹配严格 | 例如 int 和 size_t 是不同类型,不能混用 |
可以偏特化 | 可对特定非类型参数进行偏特化 |
🧪 示例:非类型参数的偏特化
template<typename T, T Value>
class DefaultValue {
public:
T get() { return Value; }
};
// 偏特化一个整数为 0 的情况
template<typename T>
class DefaultValue<T, 0> {
public:
T get() { return 42; } // 特殊处理
};
DefaultValue<int, 0> dv;
std::cout << dv.get(); // 输出 42
✅ 总结一句话:
类模板的非类型参数允许你将常量值作为模板参数传入,使得模板行为可以根据这些值在编译期就确定下来。
如果你感兴趣,我还可以继续讲解:
- 非类型参数与模板元编程结合使用
- 非类型参数与别名模板、变量模板的配合
- 如何使用字符串字面量作为非类型参数(C++20 起)