一、模板类基本概念

1.1 什么是类模板

类模板是 C++ 泛型编程的核心机制,允许我们定义一个通用的类结构,该结构能与多种数据类型一起工作,而无需为每种类型重复编写代码。

类模板不是一个具体的类,而是类的 "蓝图",编译器会根据实际使用的类型参数生成具体的类实例(模板实例化)。

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
// 简单的类模板示例
template <typename T>
class Box {
private:
T content;
public:
// 构造函数
Box(T value) : content(value) {}

// 获取存储的值
T getContent() const { return content; }

// 设置新值
void setContent(T newValue) { content = newValue; }
};

// 使用示例
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");

std::cout << intBox.getContent() << std::endl; // 输出42
std::cout << strBox.getContent() << std::endl; // 输出Hello

return 0;
}

1.2 类模板的声明与定义

类模板的声明和定义通常需要放在同一个头文件中,这与普通类不同。这是因为编译器在实例化模板时需要访问完整的定义。

1
2
3
4
5
6
7
8
// 在头文件中声明并定义模板类
template <typename T>
class Calculator {
public:
// 成员函数声明与定义
T add(T a, T b) { return a + b; }
T multiply(T a, T b) { return a * b; }
};

二、模板参数推导机制

2.1 模板参数类型

类模板可以接受多种类型的参数:

  • 类型参数(typename T 或 class T)

  • 非类型参数(常量表达式)

  • 模板模板参数(以模板作为参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 包含多种参数类型的类模板
template <typename T, int Size, template <typename> class Container>
class Buffer {
private:
Container<T> data;
public:
void fill(T value) {
for (int i = 0; i < Size; ++i) {
data.push_back(value);
}
}
};

// 使用示例
Buffer<int, 10, std::vector> intBuffer;

2.2 模板参数推导规则

C++17 引入了类模板参数推导 (CTAD),允许编译器从构造函数的参数推导出模板参数类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
class Pair {
public:
T first;
T second;

Pair(T a, T b) : first(a), second(b) {}
};

// C++17之前需要显式指定类型
Pair<int> p1(1, 2);

// C++17及以后可以自动推导
Pair p2(3, 4); // 推导出Pair<int>

对于模板模板参数,需要显式指定,无法自动推导:

1
2
3
4
5
6
7
8
9
10
template <typename T, template <typename> class Container>
class Wrapper {
public:
Container<T> items;

Wrapper(std::initializer_list<T> list) : items(list) {}
};

// 必须显式指定所有模板参数
Wrapper<int, std::vector> wrapper = {1, 2, 3};

三、模板特化技术

3.1 全特化

全特化是为特定的模板参数组合提供专门的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 通用模板
template <typename T>
class Printer {
public:
void print(const T& value) {
std::cout << "General: " << value << std::endl;
}
};

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

// 使用示例
Printer<int> intPrinter;
intPrinter.print(42); // 使用通用版本

Printer<const char*> strPrinter;
strPrinter.print("Hello"); // 使用特化版本

3.2 偏特化

偏特化是为部分模板参数提供专门实现,保持其他参数的通用性:

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
// 通用模板
template <typename T, typename U>
class Pair {
public:
T first;
U second;
Pair(T a, U b) : first(a), second(b) {}
};

// 偏特化:当两个类型相同时
template <typename T>
class Pair<T, T> {
public:
T first;
T second;
Pair(T a, T b) : first(a), second(b) {}

// 额外的方法,仅适用于两个类型相同的情况
bool areEqual() const {
return first == second;
}
};

// 使用示例
Pair<int, double> p1(1, 2.5); // 使用通用版本
Pair<int, int> p2(3, 3); // 使用偏特化版本
std::cout << p2.areEqual() << std::endl; // 输出1(true)

四、泛型编程实践

4.1 容器类模板实现

实现一个简单的动态数组容器:

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
template <typename T>
class DynamicArray {
private:
T* data;
size_t size;
size_t capacity;

void resize() {
capacity *= 2;
T* newData = new T[capacity];
for (size_t i = 0; i < size; ++i) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}

public:
// 构造函数
DynamicArray() : size(0), capacity(1) {
data = new T[capacity];
}

// 析构函数
~DynamicArray() {
delete[] data;
}

// 添加元素
void push_back(const T& element) {
if (size >= capacity) {
resize();
}
data[size++] = element;
}

// 获取元素
T& operator[](size_t index) {
if (index >= size) {
throw std::out_of_range("Index out of bounds");
}
return data[index];
}

// 获取大小
size_t getSize() const {
return size;
}
};

// 使用示例
DynamicArray<std::string> stringArray;
stringArray.push_back("First");
stringArray.push_back("Second");
for (size_t i = 0; i < stringArray.getSize(); ++i) {
std::cout << stringArray[i] << std::endl;
}

五、常见问题解决方案

5.1 模板实例化错误

模板实例化错误通常表现为复杂的编译错误信息,解决方法是:

  1. 检查模板参数是否满足所有隐含要求
  2. 使用 static_assert 提供更清晰的错误信息
  3. 逐步简化代码定位问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <typename T>
class MathOperations {
public:
// 确保T支持除法运算
static T divide(T a, T b) {
static_assert(std::is_arithmetic<T>::value,
"MathOperations requires arithmetic types");
if (b == 0) {
throw std::invalid_argument("Division by zero");
}
return a / b;
}
};

// 错误使用示例
// MathOperations<std::string> strMath;
// strMath.divide("a", "b"); // 编译错误,带有清晰的提示信息

5.2 模板与继承的交互

模板类与继承结合时需要注意的问题:

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
// 基类模板
template <typename T>
class Base {
protected:
T value;
public:
Base(T v) : value(v) {}
void printBase() { std::cout << "Base: " << value << std::endl; }
};

// 派生类模板
template <typename T, typename U>
class Derived : public Base<T> {
private:
U otherValue;
public:
// 必须显式初始化基类
Derived(T v1, U v2) : Base<T>(v1), otherValue(v2) {}

void printDerived() {
// 访问基类成员时需要使用this指针或显式限定
std::cout << "Derived: " << this->value << ", " << otherValue << std::endl;
}
};

// 使用示例
Derived<int, std::string> d(42, "test");
d.printBase(); // 输出Base: 42
d.printDerived(); // 输出Derived: 42, test