在C++中,template关键字用于定义模板,它是实现泛型编程的核心。
模板基本语法
函数模板
cpp
#include <iostream>
// 使用模板
template <typename T>
T my_max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int a = 5, b = 10;
double x = 3.14, y = 2.71;
// 编译器自动推导 T 的类型
std::cout << "my_max(a, b) = " << my_max(a, b) << std::endl; // T = int
std::cout << "my_max(x, y) = " << my_max(x, y) << std::endl; // T = double
// 也可以显式指定类型
std::cout << "my_max<double>(a, b) = " << my_max<double>(a, b) << std::endl; // T = double
return 0;
}
T是什么?
-
T 是一个模板类型参数,它是一个占位符
-
在编译时,编译器会根据调用时传入的参数类型自动推导 T 的具体类型
-
T 可以是任何支持 > 运算符的类型(int, double, string 等)
类模板
cpp
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept> // 用于std::runtime_error
template <typename T>// T是类型参数,可以是任何数据类型
class Stack {
private:
std::vector<T> elements;// 使用std::vector作为底层存储容器
public:
// 入栈操作
void push(const T& value) {
elements.push_back(value);// 将元素添加到vector末尾
}
// 出栈操作
T pop() {
if (elements.empty()) {
throw std::runtime_error("Stack is empty"); // 栈空时抛出异常
}
T value = elements.back();// 获取栈顶元素
elements.pop_back();//移除栈顶元素
return value;//返回栈顶元素
}
// 检查栈是否为空
bool empty() const {
return elements.empty();
}
// 添加更多实用方法
T top() const {
if (elements.empty()) {
throw std::runtime_error("Stack is empty");
}
return elements.back();
}
size_t size() const {
return elements.size();
}
};
int main() {
// 创建整数栈
Stack<int> intStack;
intStack.push(1);
intStack.push(2);
intStack.push(3);
std::cout << "整数栈大小: " << intStack.size() << std::endl;
std::cout << "栈顶元素: " << intStack.top() << std::endl;
// 弹出所有元素
while (!intStack.empty()) {
std::cout << "弹出: " << intStack.pop() << std::endl;
}
// 创建字符串栈
Stack<std::string> stringStack;
stringStack.push("Hello");
stringStack.push("World");
stringStack.push("C++");
std::cout << "\n字符串栈内容:" << std::endl;
while (!stringStack.empty()) {
std::cout << stringStack.pop() << std::endl;
}
return 0;
}
/*
输出结果:
整数栈大小: 3
栈顶元素: 3
弹出: 3
弹出: 2
弹出: 1
字符串栈内容:
C++
World
Hello
*/
模板参数 T 的作用
T 是一个类型占位符,在实例化时会被具体类型替换:
-
Stack → T 变成 int
-
Stackstd::string → T 变成 std::string
-
Stack → T 变成 double
模板参数类型
类型参数(typename/class)
cpp
// 定义有两个类型参数的模板类
template <typename T1, class T2> // typename 和 class 在模板参数中完全等价
class Pair {
private:
T1 first; // 第一个数据成员,类型为 T1
T2 second; // 第二个数据成员,类型为 T2
public:
// 构造函数:用两个参数初始化 first 和 second
Pair(T1 f, T2 s) : first(f), second(s) {}
// 获取第一个元素的常量成员函数
T1 getFirst() const { return first; }
// 获取第二个元素的常量成员函数
T2 getSecond() const { return second; }
};
// 使用
Pair<int, std::string> person(1, "Alice");
-
T1 和 T2 都是类型参数
-
typename 和 class 在模板参数中完全等价,可以互换使用
-
每个实例化的 Pair 类都会绑定到特定的 T1 和 T2 类型
完整示例
cpp
#include <iostream>
#include <string>
template <typename T1, class T2>
class Pair {
private:
T1 first;
T2 second;
public:
Pair(T1 f, T2 s) : first(f), second(s) {}
T1 getFirst() const { return first; }
T2 getSecond() const { return second; }
// 可以添加设置方法
void setFirst(T1 f) { first = f; }
void setSecond(T2 s) { second = s; }
// 可以添加显示方法
void display() const {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}
};
int main() {
// 示例1:整数和字符串的Pair
Pair<int, std::string> person(1, "Alice");
std::cout << "ID: " << person.getFirst() << ", Name: " << person.getSecond() << std::endl;
// 示例2:字符串和浮点数的Pair
Pair<std::string, double> product("Apple", 2.99);
std::cout << "Product: " << product.getFirst() << ", Price: $" << product.getSecond() << std::endl;
// 示例3:相同类型的Pair
Pair<double, double> coordinates(3.14, 2.71);
std::cout << "Coordinates: (" << coordinates.getFirst() << ", " << coordinates.getSecond() << ")" << std::endl;
// 示例4:使用显示方法
Pair<int, std::string> student(101, "Bob");
student.display();
return 0;
}
/*
输出结果:
ID: 1, Name: Alice
Product: Apple, Price: $2.99
Coordinates: (3.14, 2.71)
(101, Bob)
*/
非类型模板参数
这是一个固定大小数组模板类,使用了非类型模板参数来指定数组大小。
cpp
// 模板参数:一个类型参数 T 和一个非类型参数 Size
template <typename T, int Size>
class FixedArray {
private:
T data[Size]; // 固定大小的数组,大小在编译时确定
public:
// 重载下标运算符(非常量版本)
T& operator[](int index) {
return data[index]; // 返回元素的引用,允许修改
}
// 重载下标运算符(常量版本)
const T& operator[](int index) const {
return data[index]; // 返回常量引用,不允许修改
}
/*
const 关键字表示这个函数不会修改对象状态
用于常量对象的访问
*/
// 静态常量成员函数,返回数组大小
static constexpr int size() { return Size; }
};
// 使用
FixedArray<double, 10> array; // 创建大小为10的double数组
模板默认参数
cpp
template <typename T = int, int Size = 100>
class Container {
// 实现
};
// 使用
Container<> container1; // 使用默认参数 T=int, Size=100
Container<double> container2; // T=double, Size=100
Container<double, 50> container3; // T=double, Size=50
模板特化*****
全特化
cpp
#include <iostream>
#include <string>
// 主模板
template <typename T>
class Printer {
public:
void print(const T& value) {
std::cout << "Generic: " << value << std::endl;
}
};
// 全特化 - 为 std::string 提供特殊实现
template <>
class Printer<std::string> {
public:
void print(const std::string& value) {
std::cout << "String: \"" << value << "\"" << std::endl;
}
};
// 可以为更多类型提供特化
template <>
class Printer<double> {
public:
void print(const double& value) {
std::cout << "Double: " << std::fixed << value << std::endl;
}
};
// 为指针类型提供特化
template <typename T>
class Printer<T*> {
public:
void print(T* value) {
if (value) {
std::cout << "Pointer: " << *value << std::endl;
} else {
std::cout << "Pointer: nullptr" << std::endl;
}
}
};
int main() {
// 使用主模板
Printer<int> intPrinter;
intPrinter.print(42); // 输出: Generic: 42
// 使用 std::string 特化
Printer<std::string> strPrinter;
strPrinter.print("Hello"); // 输出: String: "Hello"
// 使用 double 特化
Printer<double> doublePrinter;
doublePrinter.print(3.14159); // 输出: Double: 3.141590
// 使用指针特化
int x = 100;
Printer<int*> ptrPrinter;
ptrPrinter.print(&x); // 输出: Pointer: 100
Printer<int*> nullPtrPrinter;
nullPtrPrinter.print(nullptr); // 输出: Pointer: nullptr
return 0;
}
-
针对具体类型的特化
-
完全重写类的实现
-
可以有不同的成员函数和数据成员
偏特化
cpp
// 主模板
template <typename T1, typename T2>
class MyClass {
public:
void print() {
std::cout << "Primary template" << std::endl;
}
};
/*
接受两个任意类型参数
提供通用实现
*/
// 偏特化 - 两个类型相同的情况
template <typename T>
class MyClass<T, T> {
public:
void print() {
std::cout << "Both types are the same" << std::endl;
}
};
/*
当 T1 和 T2 是相同类型时使用
语法:MyClass<T, T>
*/
// 偏特化 - 第二个类型为int的情况
template <typename T>
class MyClass<T, int> {
public:
void print() {
std::cout << "Second type is int" << std::endl;
}
};
/*
当第二个模板参数是 int 时使用
语法:MyClass<T, int>
*/
// 使用
int main() {
MyClass<int, double> obj1;
obj1.print(); // Primary template
MyClass<int, int> obj2;
obj2.print(); // Both types are the same
MyClass<double, int> obj3;
obj3.print(); // Second type is int
return 0;
}
完整示例
cpp
#include <iostream>
// 主模板
template <typename T1, typename T2>
class MyClass {
public:
void print() {
std::cout << "Primary template" << std::endl;
}
};
// 偏特化 - 两个类型相同的情况
template <typename T>
class MyClass<T, T> {
public:
void print() {
std::cout << "Both types are the same" << std::endl;
}
};
// 偏特化 - 第二个类型为int的情况
template <typename T>
class MyClass<T, int> {
public:
void print() {
std::cout << "Second type is int" << std::endl;
}
};
// 偏特化 - 第一个类型为指针的情况
template <typename T1, typename T2>
class MyClass<T1*, T2> {
public:
void print() {
std::cout << "First type is a pointer" << std::endl;
}
};
int main() {
// 使用主模板
MyClass<int, double> obj1;
obj1.print(); // Primary template
// 使用第一个偏特化(类型相同)
MyClass<int, int> obj2;
obj2.print(); // Both types are the same
// 使用第二个偏特化(第二个参数为int)
MyClass<double, int> obj3;
obj3.print(); // Second type is int
// 使用第三个偏特化(第一个参数为指针)
MyClass<int*, double> obj4;
obj4.print(); // First type is a pointer
// 测试优先级:同时匹配多个偏特化时选择最特化的
MyClass<int*, int> obj5; // 匹配:指针特化和int特化
obj5.print(); // 输出:First type is a pointer(指针特化更具体)
return 0;
}
全特化和偏特化的区别
全特化(Full Specialization)
- 定义:为模板的所有参数提供具体的类型
- 语法:template<> class ClassName<SpecificTypes...>
cpp
// 主模板
template <typename T1, typename T2>
class Example {
public:
void print() { std::cout << "Primary" << std::endl; }
};
// 全特化 - 所有参数都具体指定
template <>
class Example<int, std::string> {
public:
void print() { std::cout << "Full specialization" << std::endl; }
};
// 使用
Example<int, std::string> obj; // 使用全特化版本
特点:
-
所有模板参数都被具体类型替换
-
是一个完全具体的类,不再是模板
-
可以完全重新设计实现,不依赖主模板
偏特化(Partial Specialization)
- 定义:只对部分模板参数提供具体类型或施加约束
- 语法:template<typename...> class ClassName<PartialTypes...>
cpp
// 主模板
template <typename T1, typename T2>
class Example {
public:
void print() { std::cout << "Primary" << std::endl; }
};
// 偏特化 - 第二个参数固定为int
template <typename T>
class Example<T, int> {
public:
void print() { std::cout << "T and int" << std::endl; }
};
// 偏特化 - 两个参数类型相同
template <typename T>
class Example<T, T> {
public:
void print() { std::cout << "Same types" << std::endl; }
};
// 使用
Example<double, int> obj1; // 使用 T and int 偏特化
Example<float, float> obj2; // 使用 Same types 偏特化
Example<double, std::string> obj3; // 使用主模板
特点:
-
仍然是模板,有未指定的模板参数
-
对参数施加模式约束(如类型相同、指针类型等)
-
必须遵循主模板的基本结构
对比表格

小结:
-
全特化:为特定类型组合提供完全定制的实现
-
偏特化:为一类类型模式提供特殊实现
-
选择规则:全特化 > 偏特化 > 主模板
-
偏特化更灵活:可以定义类型关系(相同、指针、引用等模式)
可变参数模板
基本用法
这是一个使用可变参数模板 (Variadic Templates)和递归展开实现的通用打印函数,可以接受任意数量、任意类型的参数。
cpp
// 递归终止函数
void print() {
std::cout << std::endl;
}
/*
当参数包为空时调用此函数
输出换行符并结束递归
*/
// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " ";
print(args...); // 递归调用
}
/*
typename... Args:参数包,表示0个或多个类型
Args... args:函数参数包,表示0个或多个参数
递归调用:每次处理一个参数,剩余参数继续递归
*/
// 使用
int main() {
print(1, 2.5, "hello", 'a'); // 输出: 1 2.5 hello a
return 0;
}
/*
// 第一次调用
print<int, double, const char*, char>(1, 2.5, "hello", 'a')
→ 输出: "1 "
→ 调用: print(2.5, "hello", 'a')
// 第二次调用
print<double, const char*, char>(2.5, "hello", 'a')
→ 输出: "2.5 "
→ 调用: print("hello", 'a')
// 第三次调用
print<const char*, char>("hello", 'a')
→ 输出: "hello "
→ 调用: print('a')
// 第四次调用
print<char>('a')
→ 输出: "a "
→ 调用: print()
// 第五次调用
print()
→ 输出: 换行符
→ 递归终止
*/
折叠表达式(C++17)
折叠表达式是 C++17 引入的新特性,它允许对参数包中的所有元素应用二元运算符,无需递归展开。
cpp
// C++17 折叠表达式
// 打印函数的折叠表达式版本
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 一元右折叠
}
/*
展开过程;
// 调用 print(1, 2, 3) 展开为:
std::cout << 1 << 2 << 3 << std::endl;
// 相当于:
((std::cout << 1) << 2) << 3 << std::endl;
*/
//求和函数的折叠表达式版本
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元右折叠
}
/*
调用 sum(1, 2, 3, 4, 5) 展开为:
1 + (2 + (3 + (4 + 5)));
计算结果:15
*/
// 使用
int main() {
print(1, 2, 3); // 输出: 123
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出: 15
return 0;
}
折叠表达式的四种形式
- 一元右折叠 (args op ...)
cpp
template <typename... Args>
auto right_fold(Args... args) {
return (args + ...); // 展开为: arg1 + (arg2 + (arg3 + ...))
}
- 一元左折叠 (... op args)
cpp
template <typename... Args>
auto left_fold(Args... args) {
return (... + args); // 展开为: ((arg1 + arg2) + arg3) + ...
}
- 二元右折叠 (args op ... op init)
cpp
template <typename... Args>
auto binary_right_fold(Args... args) {
return (args + ... + 0); // 展开为: arg1 + (arg2 + (arg3 + 0))
}
- 二元左折叠 (init op ... op args)
cpp
template <typename... Args>
auto binary_left_fold(Args... args) {
return (0 + ... + args); // 展开为: ((0 + arg1) + arg2) + arg3
}
完整示例
cpp
#include <iostream>
// C++17 折叠表达式
template <typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 一元右折叠
}
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // 一元右折叠
}
// 更多折叠表达式示例
template <typename... Args>
auto product(Args... args) {
return (args * ...); // 一元右折叠:乘法
}
template <typename... Args>
bool all_true(Args... args) {
return (args && ...); // 一元右折叠:逻辑与
}
template <typename... Args>
bool any_true(Args... args) {
return (args || ...); // 一元右折叠:逻辑或
}
int main() {
// 测试打印函数
print(1, 2, 3); // 输出: 123
print("Hello", " ", "World", "!"); // 输出: Hello World!
print(1, " + ", 2, " = ", 3); // 输出: 1 + 2 = 3
// 测试求和函数
std::cout << "Sum: " << sum(1, 2, 3, 4, 5) << std::endl; // 输出: 15
std::cout << "Sum: " << sum(1.5, 2.5, 3.5) << std::endl; // 输出: 7.5
// 测试其他折叠操作
std::cout << "Product: " << product(2, 3, 4) << std::endl; // 输出: 24
std::cout << "All true: " << all_true(true, true, true) << std::endl; // 输出: 1
std::cout << "Any true: " << any_true(false, true, false) << std::endl; // 输出: 1
return 0;
}
模板高级特性
SFINAE(替换失败不是错误)
std::enable_if 工作原理
cpp
template<bool Condition, typename T = void>
struct enable_if;
// 特化:当Condition为true时
template<typename T>
struct enable_if<true, T> {
using type = T; // 定义type成员
};
// 特化:当Condition为false时
template<typename T>
struct enable_if<false, T> {
// 没有type成员!这会导致SFINAE
};
SFINAE(Substitution Failure Is Not An Error)机制:
-
当模板参数推导失败时,编译器不会报错,而是从候选函数中移除该模板
-
只有满足条件的模板才会被实例化
使用 constexpr if(C++17)
cpp
// 方法2:使用 constexpr if (C++17)
template <typename T>
auto process_modern(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point: " << value << std::endl;
} else {
std::cout << "Other type" << std::endl;
}
return value;
}
constexpr if 特点
-
编译期求值:条件在编译时确定
-
分支消除:只有满足条件的分支会被编译
-
语法简洁:不需要多个模板重载
完整示例
cpp
#include <iostream>
#include <type_traits>
#include <string>
template <typename T>
auto process_modern(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Integral: " << value;
if constexpr (sizeof(T) == 1) {
std::cout << " (char type)";
} else if constexpr (sizeof(T) == 4) {
std::cout << " (32-bit int)";
}
std::cout << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Floating point: " << value;
if constexpr (std::is_same_v<T, float>) {
std::cout << " (float)";
} else if constexpr (std::is_same_v<T, double>) {
std::cout << " (double)";
}
std::cout << std::endl;
} else if constexpr (std::is_pointer_v<T>) {
std::cout << "Pointer to: " << *value << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: \"" << value << "\" (length: " << value.length() << ")" << std::endl;
} else {
std::cout << "Other type: " << value << std::endl;
}
return value;
}
int main() {
process_modern(42); // Integral
process_modern(3.14f); // Floating point (float)
process_modern(2.718); // Floating point (double)
int y = 200;
process_modern(&y); // Pointer
process_modern(std::string("Hello")); // String
process_modern(true); // Other type (bool)
return 0;
}
两种方法对比
SFINAE (std::enable_if) 方法
优点:
-
兼容 C++11
-
明确的函数重载,编译器可以更好地优化
-
可以用于类模板特化
缺点:
-
语法复杂,可读性差
-
需要写多个重载版本
-
错误信息不友好
constexpr if 方法
优点:
-
语法简洁,可读性好
-
只需要一个函数模板
-
更好的错误信息
-
可以嵌套使用
缺点:
-
需要 C++17 支持
-
所有分支必须在语法上有效(即使不被编译)
模板元编程
cpp
// 编译期计算阶乘
template <int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
/*
这是一个递归模板,通过 Factorial<N-1> 递归调用
static constexpr 表示这是一个编译期常量
计算公式:N! = N × (N-1)!
*/
//全特化(终止条件)
template <>
struct Factorial<0> {
static constexpr int value = 1;
};
/*
为 N=0 提供特化,定义递归终止条件
数学定义:0! = 1
*/
// 使用
int main() {
std::cout << Factorial<5>::value << std::endl; // 输出: 120
return 0;
}
//当计算 Factorial<5>::value 时,编译器展开过程如下:
/*
Factorial<5>::value
= 5 * Factorial<4>::value
= 5 * (4 * Factorial<3>::value)
= 5 * (4 * (3 * Factorial<2>::value))
= 5 * (4 * (3 * (2 * Factorial<1>::value)))
= 5 * (4 * (3 * (2 * (1 * Factorial<0>::value))))
= 5 * (4 * (3 * (2 * (1 * 1)))) // Factorial<0>::value = 1
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
*/
完整代码
cpp
#include <iostream>
// 编译期计算阶乘
template <int N>
struct Factorial {
static constexpr int value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
static constexpr int value = 1;
};
int main() {
std::cout << "Factorial<0>::value = " << Factorial<0>::value << std::endl; // 1
std::cout << "Factorial<1>::value = " << Factorial<1>::value << std::endl; // 1
std::cout << "Factorial<5>::value = " << Factorial<5>::value << std::endl; // 120
std::cout << "Factorial<10>::value = " << Factorial<10>::value << std::endl; // 3628800
// 编译期验证
static_assert(Factorial<0>::value == 1, "0! should be 1");
static_assert(Factorial<5>::value == 120, "5! should be 120");
static_assert(Factorial<10>::value == 3628800, "10! should be 3628800");
// 可以在编译期常量需要的地方使用
int array[Factorial<4>::value]; // 创建大小为24的数组
std::cout << "数组大小: " << sizeof(array)/sizeof(array[0]) << std::endl;
return 0;
}
更多示例
- 斐波那契数列
cpp
// 编译期计算斐波那契数列
template <int N>
struct Fibonacci {
static constexpr int value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value;
};
template <>
struct Fibonacci<0> {
static constexpr int value = 0;
};
template <>
struct Fibonacci<1> {
static constexpr int value = 1;
};
int main() {
std::cout << "Fibonacci<0> = " << Fibonacci<0>::value << std::endl; // 0
std::cout << "Fibonacci<1> = " << Fibonacci<1>::value << std::endl; // 1
std::cout << "Fibonacci<10> = " << Fibonacci<10>::value << std::endl; // 55
return 0;
}
- 最大公约数(GCD)
cpp
// 编译期计算最大公约数
template <int A, int B>
struct GCD {
static constexpr int value = GCD<B, A % B>::value;
};
template <int A>
struct GCD<A, 0> {
static constexpr int value = A;
};
int main() {
std::cout << "GCD(48, 18) = " << GCD<48, 18>::value << std::endl; // 6
std::cout << "GCD(1071, 462) = " << GCD<1071, 462>::value << std::endl; // 21
return 0;
}
概念(C++20)
cpp
// C++20 概念
template <typename T>
concept Integral = std::is_integral_v<T>;
/*
使用类型特征 std::is_integral_v 来定义概念
满足条件的类型:int, long, short, char, bool 等
不满足的类型:float, double, std::string 等
*/
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
/*
使用 requires 表达式定义概念
检查类型 T 是否支持 + 运算符
要求 a + b 的返回类型必须与 T 相同
*/
// 使用概念约束模板
template <Integral T>
T add_integral(T a, T b) {
return a + b;
}
template <Addable T>
T add_any(T a, T b) {
return a + b;
}
// 使用
int main() {
add_integral(5, 3); // OK
// add_integral(3.14, 2.71); // 编译错误:不满足 Integral 概念
add_any(5, 3); // OK
add_any(std::string("hello"), std::string("world")); // OK
return 0;
}
完整代码
cpp
#include <iostream>
#include <type_traits>
#include <string>
#include <concepts> // C++20 概念头文件
// 方法1:使用类型特征定义概念
template <typename T>
concept Integral = std::is_integral_v<T>;
template <typename T>
concept FloatingPoint = std::is_floating_point_v<T>;
// 方法2:使用 requires 表达式定义概念
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template <typename T>
concept Subtractable = requires(T a, T b) {
{ a - b } -> std::same_as<T>;
};
template <typename T>
concept Printable = requires(T a) {
{ std::cout << a } -> std::same_as<std::ostream&>;
};
// 方法3:使用标准库预定义概念
template <typename T>
concept Arithmetic = std::integral<T> || std::floating_point<T>;
// 使用概念约束模板函数
template <Integral T>
T add_integral(T a, T b) {
std::cout << "Adding integers: " << a << " + " << b;
T result = a + b;
std::cout << " = " << result << std::endl;
return result;
}
template <Addable T>
T add_any(T a, T b) {
std::cout << "Adding addable types: " << a << " + " << b;
T result = a + b;
std::cout << " = " << result << std::endl;
return result;
}
// 多个概念组合
template <typename T>
requires Addable<T> && Subtractable<T>
T calculate(T a, T b) {
return (a + b) * (a - b);
}
// 概念用于自动类型推导
Printable auto print_value(Printable auto value) {
std::cout << "Value: " << value << std::endl;
return value;
}
int main() {
// 测试 Integral 概念
add_integral(5, 3); // OK
// add_integral(3.14, 2.71); // 编译错误:不满足 Integral 概念
// 测试 Addable 概念
add_any(5, 3); // OK
add_any(std::string("hello"), std::string("world")); // OK
// 测试多个概念组合
std::cout << "Calculate: " << calculate(10, 3) << std::endl; // (10+3)*(10-3) = 91
// 测试自动类型推导
print_value(42); // OK
print_value("Hello"); // OK
// print_value(std::vector<int>{}); // 编译错误:不满足 Printable 概念
return 0;
}
模板实战示例
工厂模式模板
这是一个使用C++模板实现的通用工厂模式(Generic Factory Pattern),可以动态创建继承自同一基类的不同派生类对象。
cpp
template <typename Base>
class Factory {
private:
using Creator = std::function<std::unique_ptr<Base>()>;
/*
定义了一个函数类型,该函数无参数,返回 std::unique_ptr<Base>
每个注册的类都会对应一个这样的创建函数
*/
std::unordered_map<std::string, Creator> creators;
/*
使用字符串作为键,创建函数作为值
例如:"Circle" → 创建 Circle 对象的函数
*/
public:
template <typename Derived>
void registerClass(const std::string& name) {
creators[name] = []() -> std::unique_ptr<Base> {
return std::make_unique<Derived>();
};
}
/*
使用 lambda 表达式捕获 Derived 类型信息
当调用时,创建 Derived 对象并向上转型为 Base 指针
*/
std::unique_ptr<Base> create(const std::string& name) {
auto it = creators.find(name);
if (it != creators.end()) {
return it->second();
}
return nullptr;
}
};
完整代码
cpp
#include <iostream>
#include <memory>
#include <unordered_map>
#include <functional>
#include <string>
template <typename Base>
class Factory {
private:
using Creator = std::function<std::unique_ptr<Base>()>;
std::unordered_map<std::string, Creator> creators;
public:
template <typename Derived>
void registerClass(const std::string& name) {
creators[name] = []() -> std::unique_ptr<Base> {
return std::make_unique<Derived>();
};
}
std::unique_ptr<Base> create(const std::string& name) {
auto it = creators.find(name);
if (it != creators.end()) {
return it->second();
}
return nullptr;
}
// 添加更多实用方法
bool isRegistered(const std::string& name) const {
return creators.find(name) != creators.end();
}
std::vector<std::string> getRegisteredNames() const {
std::vector<std::string> names;
for (const auto& pair : creators) {
names.push_back(pair.first);
}
return names;
}
};
// 基类和派生类定义
class Shape {
public:
virtual void draw() = 0;
virtual std::string getName() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circle ○" << std::endl;
}
std::string getName() const override { return "Circle"; }
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing Square □" << std::endl;
}
std::string getName() const override { return "Square"; }
};
class Triangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Triangle △" << std::endl;
}
std::string getName() const override { return "Triangle"; }
};
int main() {
// 创建形状工厂
Factory<Shape> shapeFactory;
// 注册形状类
shapeFactory.registerClass<Circle>("Circle");
shapeFactory.registerClass<Square>("Square");
shapeFactory.registerClass<Triangle>("Triangle");
// 显示已注册的类型
std::cout << "Registered shapes: ";
for (const auto& name : shapeFactory.getRegisteredNames()) {
std::cout << name << " ";
}
std::cout << std::endl;
// 动态创建对象
auto circle = shapeFactory.create("Circle");
auto square = shapeFactory.create("Square");
auto triangle = shapeFactory.create("Triangle");
auto unknown = shapeFactory.create("Hexagon"); // 未注册的类型
// 使用创建的对象
if (circle) {
circle->draw();
std::cout << "Created: " << circle->getName() << std::endl;
}
if (square) {
square->draw();
std::cout << "Created: " << square->getName() << std::endl;
}
if (triangle) {
triangle->draw();
std::cout << "Created: " << triangle->getName() << std::endl;
}
if (!unknown) {
std::cout << "Failed to create unknown shape: Hexagon" << std::endl;
}
return 0;
}
工厂模式的工作原理
- 注册阶段:将类名与创建函数关联
cpp
factory.registerClass<Circle>("Circle");
// 相当于:creators["Circle"] = 创建Circle对象的函数
- 创建阶段:根据名称查找并调用对应的创建函数
cpp
auto shape = factory.create("Circle");
// 查找 "Circle" → 调用对应的lambda → 返回 new Circle()
策略模式模板
cpp
// 排序策略模板
template <typename T, typename Compare = std::less<T>>
/*
1. 模板参数
typename T:要排序的元素类型
typename Compare = std::less<T>:比较策略,默认是升序
*/
class Sorter {
private:
Compare comp;// 比较策略对象
/*
存储比较策略的实例
可以是函数对象、函数指针或lambda表达式
*/
public:
void sort(std::vector<T>& vec) {
std::sort(vec.begin(), vec.end(), comp);// 使用策略对象进行排序
}
};
完整代码
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
// 排序策略模板
template <typename T, typename Compare = std::less<T>>
class Sorter {
private:
Compare comp;
public:
void sort(std::vector<T>& vec) {
std::sort(vec.begin(), vec.end(), comp);
}
// 添加更多实用方法
void sort_range(std::vector<T>& vec, size_t start, size_t end) {
if (start < end && end <= vec.size()) {
std::sort(vec.begin() + start, vec.begin() + end, comp);
}
}
bool is_sorted(const std::vector<T>& vec) const {
return std::is_sorted(vec.begin(), vec.end(), comp);
}
};
int main() {
std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};
// 升序排序
Sorter<int, std::less<int>> ascendingSorter;
ascendingSorter.sort(numbers);
std::cout << "升序排序: ";
for (int n : numbers) std::cout << n << " ";
std::cout << std::endl;
// 降序排序
Sorter<int, std::greater<int>> descendingSorter;
descendingSorter.sort(numbers);
std::cout << "降序排序: ";
for (int n : numbers) std::cout << n << " ";
std::cout << std::endl;
return 0;
}
使用Lambda表达式作为策略
cpp
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<std::pair<int, std::string>> pairs = {
{3, "three"}, {1, "one"}, {4, "four"}, {1, "one-again"}, {2, "two"}
};
// 使用lambda表达式作为比较策略
auto pairCompare = [](const auto& a, const auto& b) {
// 先按数字排序,数字相同按字符串排序
if (a.first != b.first) {
return a.first < b.first;
}
return a.second < b.second;
};
// C++17 CTAD (Class Template Argument Deduction)
Sorter sorter{pairCompare};
sorter.sort(pairs);
std::cout << "排序后的键值对:" << std::endl;
for (const auto& p : pairs) {
std::cout << "{" << p.first << ", \"" << p.second << "\"}" << std::endl;
}
return 0;
}
策略模式的优势
-
灵活性:可以在运行时或编译时切换排序策略
-
可扩展性:轻松添加新的排序策略
-
复用性:同一套排序逻辑支持不同比较规则
-
类型安全:编译时检查比较策略的兼容性
-
性能:无虚函数开销,编译器可以内联优化
模板最佳实践
-
将模板实现放在头文件中:模板需要在编译时实例化
-
使用概念约束模板参数(C++20):提高代码可读性和错误信息
-
避免过度使用模板:模板会增加编译时间和代码体积
-
使用SFINAE或概念进行条件编译:提供更好的类型安全
-
考虑模板特化:为特定类型提供优化实现
模板是C++最强大的特性之一,合理使用可以编写出高效、类型安全且可重用的代码。