目录
在 C++ 泛型编程中,类模板是构建通用数据结构和算法的核心工具。类模板不仅允许我们定义与类型无关的类,还支持丰富的成员特性,如成员函数、非类型参数、友元声明、成员模板和静态成员等。
一、类模板成员函数
1.1 类模板成员函数的定义与实例化
类模板的成员函数本身也是模板,需要在使用时根据上下文实例化。成员函数可以在类体内定义(隐式内联),也可以在类体外定义。
示例:类体内定义成员函数
template<typename T>
class Stack {
private:
std::vector<T> elements;
public:
// 类体内定义成员函数
void push(const T& value) {
elements.push_back(value);
}
T pop() {
if (elements.empty()) throw std::out_of_range("Stack is empty");
T top = elements.back();
elements.pop_back();
return top;
}
};
示例:类体外定义成员函数
template<typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& value);
T pop();
};
// 类体外定义成员函数
template<typename T>
void Stack<T>::push(const T& value) {
elements.push_back(value);
}
template<typename T>
T Stack<T>::pop() {
if (elements.empty()) throw std::out_of_range("Stack is empty");
T top = elements.back();
elements.pop_back();
return top;
}
1.2 成员函数的实例化规则
类模板的成员函数只有在被调用或取地址时才会被实例化。这种延迟实例化机制可以避免不必要的代码生成。
template<typename T>
class Container {
public:
void printType() {
std::cout << typeid(T).name() << std::endl;
}
void unusedFunction() {
// 包含无效代码,但未被调用时不会实例化
T::undefined_member; // 仅在调用时触发编译错误
}
};
int main() {
Container<int> c;
c.printType(); // 实例化printType<int>()
// c.unusedFunction(); // 若取消注释,将触发编译错误
return 0;
}
二、非类型形参的模板实参
类模板不仅可以接受类型参数,还可以接受非类型参数(如整数、指针、引用等)。非类型参数在编译时必须是常量表达式。
2.1 非类型参数的基本语法
template<typename T, size_t N>
class Array {
private:
T data[N];
public:
T& operator[](size_t index) { return data[index]; }
const T& operator[](size_t index) const { return data[index]; }
size_t size() const { return N; }
};
// 使用非类型参数实例化
Array<int, 10> intArray; // 创建包含10个整数的数组
Array<double, 5> doubleArray; // 创建包含5个双精度浮点数的数组
2.2 非类型参数的限制
非类型参数必须满足以下条件:
- 整数类型(包括枚举)
- 指针或引用类型
- 成员指针类型
- 字面类型(C++20 起)
// 错误示例:非类型参数不能是浮点类型
// template<typename T, double N> class InvalidArray {}; // 编译错误
// 正确示例:非类型参数可以是指针
template<typename T, T* ptr>
class PointerWrapper {
// ...
};
int globalVar = 42;
PointerWrapper<int, &globalVar> wrapper;
三、类模板中的友元声明
类模板中的友元声明允许外部类、函数或其他模板访问其私有成员。友元声明可以分为三类:普通友元、模板友元(特定实例化或所有实例化)和成员模板友元。
3.1 普通友元声明
template<typename T>
class Container {
private:
T value;
public:
Container(const T& val) : value(val) {}
// 普通友元函数
friend void printValue(const Container<T>& obj) {
std::cout << "Value: " << obj.value << std::endl;
}
};
// 使用友元函数
int main() {
Container<int> c(42);
printValue(c); // 输出: Value: 42
return 0;
}
3.2 模板友元声明
template<typename T> class Container; // 前向声明
// 模板函数声明
template<typename T>
void printContainer(const Container<T>& obj);
template<typename T>
class Container {
private:
T value;
public:
Container(const T& val) : value(val) {}
// 特定实例化的模板友元
friend void printContainer<T>(const Container<T>& obj);
// 所有实例化的模板友元
template<typename U>
friend void globalFriendFunction(const Container<U>& obj);
};
// 模板友元函数的定义
template<typename T>
void printContainer(const Container<T>& obj) {
std::cout << "Template friend: " << obj.value << std::endl;
}
template<typename U>
void globalFriendFunction(const Container<U>& obj) {
std::cout << "Global friend: " << obj.value << std::endl;
}
四、Queue 和 QueueItem 的友元声明示例
队列(Queue)是一种常见的数据结构,通常由节点(QueueItem)组成。通过友元声明,Queue 可以访问 QueueItem 的私有成员。
4.1 Queue 和 QueueItem 的实现
// QueueItem类:表示队列中的节点
template<typename T>
class QueueItem;
// Queue类:表示队列
template<typename T>
class Queue {
private:
QueueItem<T>* head;
QueueItem<T>* tail;
public:
Queue() : head(nullptr), tail(nullptr) {}
~Queue();
void push(const T& value);
void pop();
bool empty() const { return head == nullptr; }
T& front() { return head->value; }
const T& front() const { return head->value; }
// 声明QueueItem为友元类,允许访问其私有成员
friend class QueueItem<T>;
};
// QueueItem类的实现
template<typename T>
class QueueItem {
private:
T value;
QueueItem* next;
// 私有构造函数,只能由Queue创建
QueueItem(const T& val) : value(val), next(nullptr) {}
// 私有析构函数,只能由Queue销毁
~QueueItem() = default;
// 声明Queue为友元类,允许访问其私有成员
friend class Queue<T>;
};
// Queue类成员函数的实现
template<typename T>
Queue<T>::~Queue() {
while (!empty()) {
pop();
}
}
template<typename T>
void Queue<T>::push(const T& value) {
QueueItem<T>* newItem = new QueueItem<T>(value);
if (empty()) {
head = tail = newItem;
} else {
tail->next = newItem;
tail = newItem;
}
}
template<typename T>
void Queue<T>::pop() {
if (empty()) return;
QueueItem<T>* oldHead = head;
head = head->next;
delete oldHead;
if (head == nullptr) {
tail = nullptr;
}
}
五、成员模板
类模板的成员本身也可以是模板,称为成员模板。成员模板可以是成员函数模板或嵌套类模板。
5.1 成员函数模板
#include <iostream>
template<typename T>
class Container {
private:
T value;
public:
// 声明所有Container实例化类型为友元
template<typename U>
friend class Container; // 友元声明
Container(const T& val) : value(val) {}
// 成员函数模板(转换构造函数)
template<typename U>
Container(const Container<U>& other) : value(other.value) { // 现在可以访问other.value
std::cout << "Template conversion constructor" << std::endl;
}
T getValue() const { return value; }
};
int main() {
Container<double> d(3.14);
Container<int> i(d); // 调用模板转换构造函数
std::cout << i.getValue() << std::endl; // 输出: 3
return 0;
}
5.2 嵌套类模板
template<typename T>
class Outer {
private:
T value;
public:
Outer(const T& val) : value(val) {}
// 嵌套类模板
template<typename U>
class Inner {
private:
U innerValue;
public:
Inner(const U& val) : innerValue(val) {}
void printOuterValue(const Outer<T>& outer) {
std::cout << "Outer value: " << outer.value << std::endl;
}
};
};
// 使用嵌套类模板
int main() {
Outer<int> outer(42);
Outer<int>::Inner<double> inner(3.14);
inner.printOuterValue(outer); // 输出: Outer value: 42
return 0;
}
六、完整的 Queue 类实现
6.1 完整的 Queue 类代码
下面是一个完整的队列类实现,包含了前面讨论的各种特性:
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
#include <stdexcept>
// 前置声明
template <typename T> class Queue;
template <typename T> std::ostream& operator<<(std::ostream& os, const Queue<T>& q);
// QueueItem类模板
template <typename T>
class QueueItem {
private:
T data;
QueueItem* next;
// 构造函数设为私有,只能由Queue创建
QueueItem(const T& val) : data(val), next(nullptr) {}
// 友元声明
friend class Queue<T>;
friend std::ostream& operator<< <T>(std::ostream& os, const Queue<T>& q);
};
// Queue类模板
template <typename T>
class Queue {
private:
QueueItem<T>* head;
QueueItem<T>* tail;
public:
// 构造函数
Queue() : head(nullptr), tail(nullptr) {}
// 拷贝构造函数
Queue(const Queue& other);
// 析构函数
~Queue();
// 赋值运算符
Queue& operator=(const Queue& other);
// 入队
void push(const T& value);
// 出队
void pop();
// 获取队首元素
T& front();
const T& front() const;
// 判断队列是否为空
bool empty() const;
// 成员函数模板:从另一种类型的队列复制
template <typename U>
Queue& operator=(const Queue<U>& other);
// 友元声明
friend std::ostream& operator<< <T>(std::ostream& os, const Queue<T>& q);
};
// 拷贝构造函数的实现
template <typename T>
Queue<T>::Queue(const Queue& other) : head(nullptr), tail(nullptr) {
for (QueueItem<T>* p = other.head; p != nullptr; p = p->next) {
push(p->data);
}
}
// 析构函数的实现
template <typename T>
Queue<T>::~Queue() {
while (!empty()) {
pop();
}
}
// 赋值运算符的实现
template <typename T>
Queue<T>& Queue<T>::operator=(const Queue& other) {
if (this != &other) {
// 清空当前队列
while (!empty()) {
pop();
}
// 复制另一个队列的元素
for (QueueItem<T>* p = other.head; p != nullptr; p = p->next) {
push(p->data);
}
}
return *this;
}
// 入队操作的实现
template <typename T>
void Queue<T>::push(const T& value) {
QueueItem<T>* newItem = new QueueItem<T>(value);
if (empty()) {
head = tail = newItem;
} else {
tail->next = newItem;
tail = newItem;
}
}
// 出队操作的实现
template <typename T>
void Queue<T>::pop() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
QueueItem<T>* oldHead = head;
head = head->next;
if (head == nullptr) {
tail = nullptr;
}
delete oldHead;
}
// 获取队首元素的实现
template <typename T>
T& Queue<T>::front() {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return head->data;
}
template <typename T>
const T& Queue<T>::front() const {
if (empty()) {
throw std::underflow_error("Queue is empty");
}
return head->data;
}
// 判断队列是否为空的实现
template <typename T>
bool Queue<T>::empty() const {
return head == nullptr;
}
// 成员函数模板:从另一种类型的队列复制
template <typename T>
template <typename U>
Queue<T>& Queue<T>::operator=(const Queue<U>& other) {
// 清空当前队列
while (!empty()) {
pop();
}
// 创建一个临时队列用于复制
Queue<U> temp = other;
// 从临时队列中取出元素并添加到当前队列
while (!temp.empty()) {
push(temp.front()); // 通过公共接口访问元素
temp.pop(); // 通过公共接口访问元素
}
return *this;
}
// 友元函数:输出队列内容
template <typename T>
std::ostream& operator<<(std::ostream& os, const Queue<T>& q) {
os << "Queue: ";
for (QueueItem<T>* p = q.head; p != nullptr; p = p->next) {
os << p->data << " ";
}
return os;
}
#endif // QUEUE_H
6.2 使用示例
下面是使用这个队列类的示例代码:
#include <iostream>
#include "queue.h"
int main() {
try {
// 创建一个整数队列
Queue<int> intQueue;
// 入队操作
intQueue.push(10);
intQueue.push(20);
intQueue.push(30);
// 输出队列
std::cout << intQueue << std::endl;
// 出队操作
intQueue.pop();
std::cout << "After pop: " << intQueue << std::endl;
// 获取队首元素
std::cout << "Front element: " << intQueue.front() << std::endl;
// 创建一个字符串队列
Queue<std::string> stringQueue;
stringQueue.push("Hello");
stringQueue.push("World");
std::cout << stringQueue << std::endl;
// 使用成员函数模板进行类型转换
Queue<double> doubleQueue;
Queue<int> anotherIntQueue;
anotherIntQueue.push(1);
anotherIntQueue.push(2);
anotherIntQueue.push(3);
// 从int队列赋值到double队列
doubleQueue = anotherIntQueue;
std::cout << "Double queue: " << doubleQueue << std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
// ---------- Queue.h ----------
#ifndef QUEUE_H
#define QUEUE_H
#include <iostream>
#include <stdexcept>
// 前向声明
template<typename T> class Queue;
template<typename T> class QueueItem;
// QueueItem类:表示队列中的节点
template<typename T>
class QueueItem {
private:
T value;
QueueItem* next;
// 私有构造函数,只能由Queue创建
explicit QueueItem(const T& val) : value(val), next(nullptr) {}
// 声明Queue为友元类,允许访问其私有成员
friend class Queue<T>;
// 禁用拷贝构造和赋值运算符
QueueItem(const QueueItem&) = delete;
QueueItem& operator=(const QueueItem&) = delete;
};
// Queue类:表示队列
template<typename T>
class Queue {
private:
QueueItem<T>* head;
QueueItem<T>* tail;
static size_t instanceCount; // 静态成员:记录实例数量
public:
// 构造函数
Queue() : head(nullptr), tail(nullptr) {
++instanceCount;
std::cout << "Queue created. Total instances: " << instanceCount << std::endl;
}
// 拷贝构造函数
Queue(const Queue& other);
// 析构函数
~Queue() {
clear();
--instanceCount;
std::cout << "Queue destroyed. Total instances: " << instanceCount << std::endl;
}
// 赋值运算符
Queue& operator=(const Queue& other);
// 元素操作
void push(const T& value);
void pop();
T& front();
const T& front() const;
bool empty() const { return head == nullptr; }
size_t size() const;
void clear();
// 静态成员函数
static size_t getInstanceCount() { return instanceCount; }
// 友元函数:输出队列内容
friend std::ostream& operator<<(std::ostream& os, const Queue<T>& queue) {
os << "Queue: ";
QueueItem<T>* current = queue.head;
while (current != nullptr) {
os << current->value << " ";
current = current->next;
}
return os;
}
};
// 初始化静态成员
template<typename T>
size_t Queue<T>::instanceCount = 0;
// 拷贝构造函数的实现
template<typename T>
Queue<T>::Queue(const Queue& other) : head(nullptr), tail(nullptr) {
++instanceCount;
std::cout << "Queue copied. Total instances: " << instanceCount << std::endl;
QueueItem<T>* current = other.head;
while (current != nullptr) {
push(current->value);
current = current->next;
}
}
// 赋值运算符的实现
template<typename T>
Queue<T>& Queue<T>::operator=(const Queue& other) {
if (this != &other) {
clear();
QueueItem<T>* current = other.head;
while (current != nullptr) {
push(current->value);
current = current->next;
}
}
return *this;
}
// push操作的实现
template<typename T>
void Queue<T>::push(const T& value) {
QueueItem<T>* newItem = new QueueItem<T>(value);
if (empty()) {
head = tail = newItem;
} else {
tail->next = newItem;
tail = newItem;
}
}
// pop操作的实现
template<typename T>
void Queue<T>::pop() {
if (empty()) {
throw std::out_of_range("Queue is empty");
}
QueueItem<T>* oldHead = head;
head = head->next;
delete oldHead;
if (head == nullptr) {
tail = nullptr;
}
}
// front操作的实现
template<typename T>
T& Queue<T>::front() {
if (empty()) {
throw std::out_of_range("Queue is empty");
}
return head->value;
}
template<typename T>
const T& Queue<T>::front() const {
if (empty()) {
throw std::out_of_range("Queue is empty");
}
return head->value;
}
// size操作的实现
template<typename T>
size_t Queue<T>::size() const {
size_t count = 0;
QueueItem<T>* current = head;
while (current != nullptr) {
++count;
current = current->next;
}
return count;
}
// clear操作的实现
template<typename T>
void Queue<T>::clear() {
while (!empty()) {
pop();
}
}
#endif // QUEUE_H
七、类模板中的 static 成员
7.1 static 成员的基本概念
类模板中的 static 成员属于每个实例化的类,而不是类模板本身。每个不同的实例化类都有自己独立的 static 成员:
template <typename T>
class Counter {
private:
static int count;
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
};
// 静态成员的初始化
template <typename T>
int Counter<T>::count = 0;
int main() {
Counter<int> c1, c2;
std::cout << "Counter<int> count: " << Counter<int>::getCount() << std::endl; // 输出2
Counter<double> d1;
std::cout << "Counter<double> count: " << Counter<double>::getCount() << std::endl; // 输出1
return 0;
}
7.2 static 成员函数
类模板中的 static 成员函数也属于每个实例化的类:
template <typename T>
class Factory {
private:
static int objectCount;
public:
static T* create() {
++objectCount;
return new T();
}
static int getObjectCount() {
return objectCount;
}
};
// 静态成员的初始化
template <typename T>
int Factory<T>::objectCount = 0;
// 使用示例
class MyClass {};
int main() {
MyClass* obj1 = Factory<MyClass>::create();
MyClass* obj2 = Factory<MyClass>::create();
std::cout << "MyClass objects created: " << Factory<MyClass>::getObjectCount() << std::endl;
delete obj1;
delete obj2;
return 0;
}
7.3 static 成员与模板特化
当模板被特化时,static 成员也会被特化:
template <typename T>
class Logger {
public:
static void log(const T& value) {
std::cout << "Generic log: " << value << std::endl;
}
};
// 模板特化
template <>
class Logger<int> {
public:
static void log(const int& value) {
std::cout << "Specialized int log: " << value << std::endl;
}
};
int main() {
Logger<double>::log(3.14); // 调用通用版本
Logger<int>::log(42); // 调用特化版本
return 0;
}
八、总结与最佳实践
8.1 类模板成员的关键特性
- 类模板成员函数在使用时才会实例化,减少代码冗余
- 非类型参数允许模板接受编译时常量,增强模板灵活性
- 友元声明提供了控制访问权限的精细粒度
- 成员模板允许在类模板内部定义更灵活的模板结构
- 静态成员属于每个实例化的类,而非类模板本身
8.2 最佳实践建议
-
分离接口与实现:使用包含模型(
#include "impl.h"
)分离类模板的接口和实现,保持头文件简洁。 -
谨慎使用友元:友元会破坏封装性,尽量减少使用。如果需要,优先使用特定实例化的友元。
-
显式管理资源:在类模板中管理动态资源时(如内存、文件句柄),确保正确实现 RAII(资源获取即初始化)原则。
-
静态成员初始化:确保每个静态数据成员在类模板外部初始化,避免链接错误。
-
异常安全:在成员函数中实现强异常安全保证,特别是在涉及内存分配和资源释放的操作中。
类模板的强大之处不仅在于其类型抽象能力,更在于其丰富的成员机制能够满足各种复杂场景的需求。