第20章 高级模板编程

模板的基本概念

模板是C++中实现泛型编程的核心机制,它允许我们编写与类型无关的代码,提高代码的重用性和灵活性。

什么是模板?

模板是一种参数化的代码蓝图,它可以根据不同的类型参数生成具体的代码。C++中的模板分为两种:

  • 函数模板:生成不同类型的函数
  • 类模板:生成不同类型的类

模板的优势

  1. 代码重用:编写一次代码,适用于多种类型
  2. 类型安全:在编译时进行类型检查
  3. 性能优化:编译器可以为特定类型生成优化的代码
  4. 灵活性:可以处理各种类型,包括自定义类型

函数模板

函数模板是一种通用的函数定义,它可以操作不同类型的参数。

基本语法

1
2
3
4
 template <typename T>
T functionName(T parameters) {
// 函数体
}

其中,typename关键字可以替换为class,它们的含义相同。

示例:最大值函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

// 函数模板:求两个值的最大值
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}

int main() {
// 实例化为int类型
int i1 = 10, i2 = 20;
std::cout << "Max of " << i1 << " and " << i2 << " is " << max(i1, i2) << std::endl;

// 实例化为double类型
double d1 = 3.14, d2 = 2.71;
std::cout << "Max of " << d1 << " and " << d2 << " is " << max(d1, d2) << std::endl;

// 实例化为string类型
std::string s1 = "hello", s2 = "world";
std::cout << "Max of \"" << s1 << "\" and \"" << s2 << "\" is \"" << max(s1, s2) << "\"" << std::endl;

return 0;
}

模板参数推导

编译器可以根据函数调用的实参类型自动推导模板参数类型,无需显式指定。

1
2
3
4
5
6
7
8
// 编译器推导T为int
max(10, 20);

// 编译器推导T为double
max(3.14, 2.71);

// 编译器推导T为std::string
max(std::string("hello"), std::string("world"));

显式实例化

我们也可以显式指定模板参数类型:

1
2
3
4
5
// 显式指定T为int
max<int>(10, 20);

// 显式指定T为double
max<double>(3.14, 2.71);

多模板参数

函数模板可以有多个模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

// 多模板参数的函数模板
template<typename T1, typename T2>
void printPair(T1 first, T2 second) {
std::cout << "Pair: (" << first << ", " << second << ")" << std::endl;
}

int main() {
printPair(10, 3.14); // T1=int, T2=double
printPair("hello", true); // T1=const char*, T2=bool
printPair(100, "world"); // T1=int, T2=const char*

return 0;
}

模板默认参数

C++11及以后,函数模板可以有默认参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>

// 带默认参数的函数模板
template<typename T1, typename T2 = int>
void printWithDefault(T1 value, T2 count = 1) {
for (int i = 0; i < count; i++) {
std::cout << value << " ";
}
std::cout << std::endl;
}

int main() {
printWithDefault("Hello"); // T1=const char*, T2=int
printWithDefault(42, 3); // T1=int, T2=int
printWithDefault(3.14, 2); // T1=double, T2=int
printWithDefault(true, std::string("times")); // T1=bool, T2=std::string

return 0;
}

类模板

类模板是一种通用的类定义,它可以根据不同的类型参数生成具体的类。

基本语法

1
2
3
4
template <typename T>
class ClassName {
// 类成员
};

示例:栈类模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <vector>

// 类模板:栈
template<typename T>
class Stack {
private:
std::vector<T> elements;

public:
// 入栈
void push(const T& item) {
elements.push_back(item);
}

// 出栈
void pop() {
if (!empty()) {
elements.pop_back();
}
}

// 获取栈顶元素
T& top() {
return elements.back();
}

// 检查栈是否为空
bool empty() const {
return elements.empty();
}

// 获取栈的大小
size_t size() const {
return elements.size();
}
};

int main() {
// 整数栈
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
intStack.push(30);

std::cout << "Int Stack size: " << intStack.size() << std::endl;
std::cout << "Top element: " << intStack.top() << std::endl;

intStack.pop();
std::cout << "After pop, top element: " << intStack.top() << std::endl;

// 字符串栈
Stack<std::string> stringStack;
stringStack.push("hello");
stringStack.push("world");

std::cout << "\nString Stack size: " << stringStack.size() << std::endl;
std::cout << "Top element: " << stringStack.top() << std::endl;

return 0;
}

类模板的成员函数

类模板的成员函数可以在类内部定义,也可以在类外部定义。在类外部定义时,需要使用模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>

// 类模板
template<typename T>
class Pair {
private:
T first;
T second;

public:
Pair(T f, T s) : first(f), second(s) {}

// 内部定义的成员函数
void print() const {
std::cout << "(" << first << ", " << second << ")" << std::endl;
}

// 声明外部定义的成员函数
T getFirst() const;
T getSecond() const;
};

// 外部定义的成员函数
template<typename T>
T Pair<T>::getFirst() const {
return first;
}

template<typename T>
T Pair<T>::getSecond() const {
return second;
}

int main() {
Pair<int> intPair(10, 20);
intPair.print();
std::cout << "First: " << intPair.getFirst() << std::endl;
std::cout << "Second: " << intPair.getSecond() << std::endl;

Pair<double> doublePair(3.14, 2.71);
doublePair.print();

return 0;
}

类模板的默认参数

类模板也可以有默认参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <vector>

// 带默认参数的类模板
template<typename T, typename Container = std::vector<T>>
class Queue {
private:
Container elements;

public:
void push(const T& item) {
elements.push_back(item);
}

void pop() {
if (!empty()) {
elements.erase(elements.begin());
}
}

T& front() {
return elements.front();
}

bool empty() const {
return elements.empty();
}

size_t size() const {
return elements.size();
}
};

int main() {
// 使用默认容器(std::vector)
Queue<int> defaultQueue;
defaultQueue.push(10);
defaultQueue.push(20);
std::cout << "Default queue front: " << defaultQueue.front() << std::endl;

return 0;
}

模板特化

模板特化允许我们为特定类型提供自定义的模板实现。

全特化

全特化是为特定类型完全重写模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>

// 主模板
template<typename T>
class Printer {
public:
void print(const T& value) {
std::cout << "General template: " << value << std::endl;
}
};

// 全特化:针对int类型
template<>
class Printer<int> {
public:
void print(const int& value) {
std::cout << "Specialized for int: " << value << std::endl;
}
};

// 全特化:针对std::string类型
template<>
class Printer<std::string> {
public:
void print(const std::string& value) {
std::cout << "Specialized for string: '" << value << "'" << std::endl;
}
};

int main() {
Printer<double> doublePrinter;
doublePrinter.print(3.14); // 使用主模板

Printer<int> intPrinter;
intPrinter.print(42); // 使用int特化版本

Printer<std::string> stringPrinter;
stringPrinter.print("hello"); // 使用string特化版本

return 0;
}

偏特化

偏特化是为模板参数的子集提供自定义实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>

// 主模板:两个类型参数
template<typename T1, typename T2>
class Pair {
public:
void print() {
std::cout << "General template: T1 and T2" << std::endl;
}
};

// 偏特化:第二个参数为int
template<typename T1>
class Pair<T1, int> {
public:
void print() {
std::cout << "Partial specialization: T1 and int" << std::endl;
}
};

// 偏特化:两个参数都是指针类型
template<typename T1, typename T2>
class Pair<T1*, T2*> {
public:
void print() {
std::cout << "Partial specialization: T1* and T2*" << std::endl;
}
};

int main() {
Pair<double, std::string> p1;
p1.print(); // 使用主模板

Pair<double, int> p2;
p2.print(); // 使用第一个偏特化版本

Pair<int*, double*> p3;
p3.print(); // 使用第二个偏特化版本

return 0;
}

可变参数模板

可变参数模板允许模板接受任意数量的模板参数,这是C++11引入的特性。

基本语法

1
2
3
4
template<typename... Args>
void function(Args... args) {
// 函数体
}

其中,Args是模板参数包,args是函数参数包。

示例:打印任意数量的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

// 递归终止函数
void print() {
std::cout << std::endl;
}

// 可变参数模板函数
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归调用,打印剩余参数
}

int main() {
print(10, 3.14, "hello", true);
print("world", 42);
print();

return 0;
}

折叠表达式

C++17引入了折叠表达式,简化了可变参数模板的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

// 使用折叠表达式的可变参数模板
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl;
}

// 带分隔符的打印
template<typename... Args>
void printWithSeparator(Args... args) {
((std::cout << args << ", "), ...) << "\b\b\n";
}

int main() {
print(10, 3.14, "hello");
printWithSeparator(10, 3.14, "hello");

return 0;
}

模板元编程

模板元编程是一种在编译时执行计算的技术,它使用模板和常量表达式来生成代码。

基本概念

  • 编译时计算:在编译期间执行计算,而不是运行时
  • 元函数:返回类型或值的模板
  • 模板递归:使用模板特化作为递归终止条件

示例:编译时计算阶乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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() {
// 编译时计算
constexpr int fact5 = Factorial<5>::value;
constexpr int fact10 = Factorial<10>::value;

std::cout << "5! = " << fact5 << std::endl;
std::cout << "10! = " << fact10 << std::endl;

return 0;
}

示例:类型 traits

类型traits是一种用于查询类型属性的模板元编程技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <type_traits>

// 自定义类型trait:检查类型是否为指针
template<typename T>
struct is_pointer {
static constexpr bool value = false;
};

template<typename T>
struct is_pointer<T*> {
static constexpr bool value = true;
};

// 使用类型trait的函数模板
template<typename T>
void printTypeInfo() {
std::cout << "Type is pointer: " << is_pointer<T>::value << std::endl;
std::cout << "Type is integral: " << std::is_integral<T>::value << std::endl;
std::cout << "Type is floating point: " << std::is_floating_point<T>::value << std::endl;
}

int main() {
std::cout << "int:" << std::endl;
printTypeInfo<int>();

std::cout << "\ndouble:" << std::endl;
printTypeInfo<double>();

std::cout << "\nint*:" << std::endl;
printTypeInfo<int*>();

return 0;
}

C++20模板增强

C++20对模板系统进行了多项重要增强,使模板编程更加灵活和强大。

模板Lambda

C++20允许在lambda表达式中使用模板参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

int main() {
// 模板lambda
auto add = []<typename T>(T a, T b) {
return a + b;
};

// 使用不同类型
std::cout << "Add integers: " << add(1, 2) << std::endl;
std::cout << "Add doubles: " << add(1.5, 2.5) << std::endl;
std::cout << "Add strings: " << add(std::string("Hello"), std::string(" World")) << std::endl;

// 带约束的模板lambda
auto multiply = []<typename T>(T a, T b) requires std::is_arithmetic_v<T> {
return a * b;
};

std::cout << "Multiply integers: " << multiply(3, 4) << std::endl;
std::cout << "Multiply doubles: " << multiply(2.5, 4.0) << std::endl;

// 可变参数模板lambda
auto print = []<typename... Args>(Args&&... args) {
(std::cout << ... << args) << std::endl;
};

print("The answer is ", 42, ", and pi is ", 3.14159);

return 0;
}

概念(Concepts)

概念是C++20引入的一种约束模板参数的机制,它允许我们定义模板参数必须满足的条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <concepts>

// 定义概念:算术类型
template <typename T>
concept Arithmetic = std::is_arithmetic_v<T>;

// 定义概念:可打印类型
template <typename T>
concept Printable = requires(T t) {
{ std::cout << t } -> std::same_as<std::ostream&>;
};

// 使用概念约束模板参数
template <Arithmetic T>
T sum(T a, T b) {
return a + b;
}

template <Printable T>
void print(T value) {
std::cout << value << std::endl;
}

// 复合概念
template <typename T>
concept ArithmeticAndPrintable = Arithmetic<T> && Printable<T>;

template <ArithmeticAndPrintable T>
void printSum(T a, T b) {
std::cout << "Sum: " << sum(a, b) << std::endl;
}

int main() {
printSum(10, 20);
printSum(3.14, 2.71);

// 错误:std::string不是算术类型
// printSum(std::string("hello"), std::string("world"));

return 0;
}

Requires表达式

Requires表达式是C++20引入的一种检查模板参数是否满足特定条件的机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <concepts>

// 使用requires表达式约束模板参数
template <typename T>
requires std::integral<T> || std::floating_point<T>
T absolute(T value) {
return value < 0 ? -value : value;
}

// 结合概念使用
template <std::integral T>
T square(T value) {
return value * value;
}

// 带多个约束的模板
template <typename T>
requires std::copyable<T> && requires(T a, T b) {
{ a + b } -> std::same_as<T>;
{ a * b } -> std::same_as<T>;
}
T sumAndProduct(T a, T b) {
return a + b * a;
}

int main() {
std::cout << "Absolute value of -5: " << absolute(-5) << std::endl;
std::cout << "Absolute value of -3.14: " << absolute(-3.14) << std::endl;

std::cout << "Square of 5: " << square(5) << std::endl;

std::cout << "Sum and product of 3 and 4: " << sumAndProduct(3, 4) << std::endl;

return 0;
}

模板编程最佳实践

1. 命名约定

  • 模板参数:使用大写字母开头的名称,如TUV或更具描述性的名称如ElementType
  • 模板类:清晰地表明它们是模板,如Vector<T>Map<K, V>

2. 代码组织

  • 头文件:模板定义通常放在头文件中,因为它们需要在使用时实例化
  • 分离声明和实现:对于大型模板,可以使用显式实例化来分离声明和实现
  • 命名空间:将模板放在适当的命名空间中,避免名称冲突

3. 性能考虑

  • 模板特化:为常用类型提供特化版本,优化性能
  • 避免过度使用:模板会增加编译时间和二进制大小,避免不必要的模板使用
  • 内联:模板函数默认是内联的,合理使用可以提高性能

4. 可读性和可维护性

  • 注释:为模板参数添加注释,说明它们的预期类型和约束
  • 概念:使用C++20的概念来清晰地表达模板参数的约束
  • 简单性:保持模板简洁,避免过度复杂的模板元编程

5. 调试技巧

  • 显式实例化:使用显式实例化来捕获模板错误
  • 编译错误:理解模板编译错误的含义,通常会显示完整的模板实例化过程
  • 调试器:使用调试器查看模板实例化后的代码

常见模板编程错误

1. 模板参数推导失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 错误:无法推导T的类型
template<typename T>
void print(T a, T b) {
std::cout << a << " " << b << std::endl;
}

print(10, 3.14); // 一个是int,一个是double

// 解决方案1:显式指定模板参数
print<double>(10, 3.14);

// 解决方案2:使用两个模板参数
template<typename T1, typename T2>
void print(T1 a, T2 b) {
std::cout << a << " " << b << std::endl;
}

2. 模板实例化错误

1
2
3
4
5
6
7
// 错误:std::string没有<运算符的重载(实际上有,这里只是示例)
template<typename T>
T max(T a, T b) {
return a > b ? a : b;
}

// 如果T是没有>运算符的类型,会在实例化时出错

3. 模板特化顺序

1
2
3
4
5
6
7
8
9
10
11
12
// 错误:特化模板在使用后定义
template<typename T>
class MyClass { /* ... */ };

// 使用模板
MyClass<int> obj;

// 特化模板
template<>
class MyClass<int> { /* ... */ };

// 正确:先定义特化模板,再使用

4. 模板参数依赖

1
2
3
4
5
6
7
8
9
10
11
// 错误:依赖模板参数的名称需要使用typename
template <typename T>
void func() {
T::type x; // 错误:需要使用typename
}

// 正确:使用typename
template <typename T>
void func() {
typename T::type x;
}

模板编程的应用场景

1. 容器类

标准模板库(STL)中的容器都是模板类:

1
2
3
std::vector<int> vec;
std::map<std::string, int> map;
std::set<double> set;

2. 算法

STL中的算法都是模板函数:

1
2
3
std::sort(vec.begin(), vec.end());
std::find(vec.begin(), vec.end(), 42);
std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << ""; });

3. 智能指针

智能指针也是模板类:

1
2
std::unique_ptr<int> up(new int(42));
std::shared_ptr<std::string> sp = std::make_shared<std::string>("hello");

4. 类型 traits

类型traits用于查询和修改类型属性:

1
2
3
std::is_integral<int>::value; // true
std::is_same<int, int>::value; // true
std::remove_reference<int&>::type; // int

5. 元编程库

Boost等库提供了丰富的元编程工具:

1
2
boost::mpl::vector<int, double, std::string> types;
boost::mpl::size<types>::value; // 3

总结

模板是C++中实现泛型编程的强大工具,它允许我们编写与类型无关的代码,提高代码的重用性和灵活性。本章介绍了:

  1. 模板的基本概念:函数模板和类模板
  2. 模板特化:全特化和偏特化
  3. 可变参数模板:处理任意数量的参数
  4. 模板元编程:在编译时执行计算
  5. C++20模板增强:模板lambda、概念、requires表达式
  6. 模板编程最佳实践:命名约定、代码组织、性能考虑
  7. 常见错误:模板参数推导失败、实例化错误等
  8. 应用场景:容器、算法、智能指针等

模板编程是C++的高级特性,掌握它需要一定的实践经验。通过不断学习和使用模板,你将能够编写更加灵活、高效、可重用的C++代码。