C++学习:六个月从基础到就业——模板编程:类模板
本文是我C++学习之旅系列的第三十三篇技术文章,也是第二阶段"C++进阶特性"的第十一篇,主要介绍C++中的类模板编程。查看完整系列目录了解更多内容。
目录
引言
在上一篇文章中,我们深入探讨了C++函数模板的概念和应用。本文将继续模板编程的旅程,重点讨论类模板,这是C++泛型编程的另一个强大工具。
类模板允许我们创建通用的类,这些类能够处理各种数据类型,而不需要为每种类型编写单独的类定义。C++标准库中的容器(如std::vector
、std::map
)就是类模板的典型应用。通过类模板,我们可以编写一次代码,却能创建适用于不同数据类型的类,大大提高了代码的复用性和灵活性。
本文将详细介绍类模板的定义和使用方法、类模板的成员函数实现、模板特化、默认模板参数以及类模板的嵌套与继承等高级特性。我们还将通过实际案例展示类模板在C++编程中的应用。
类模板的基本语法
类模板定义
类模板的定义使用template
关键字,后跟尖括号中的模板参数列表,然后是类的定义:
template <typename T> // 或 template <class T>
class MyContainer {
private:
T* elements;
size_t count;
size_t capacity;
public:
MyContainer(size_t initialCapacity = 10);
~MyContainer();
void push_back(const T& element);
T& at(size_t index);
const T& at(size_t index) const;
size_t size() const { return count; }
};
这里定义了一个简单的容器类模板MyContainer
,可以存储任意类型的元素。模板参数T
在类定义中用作占位符,表示将来使用类模板时会被具体类型替换。
类模板实例化
要使用类模板,需要指定模板参数的具体类型:
int main() {
// 创建存储int的容器
MyContainer<int> intContainer;
intContainer.push_back(42);
intContainer.push_back(73);
// 创建存储string的容器
MyContainer<std::string> stringContainer;
stringContainer.push_back("Hello");
stringContainer.push_back("World");
std::cout << "intContainer[0] = " << intContainer.at(0) << std::endl;
std::cout << "stringContainer[0] = " << stringContainer.at(0) << std::endl;
return 0;
}
当我们声明MyContainer<int>
时,编译器会生成一个将T
替换为int
的类定义。同样,MyContainer<std::string>
会生成另一个将T
替换为std::string
的类定义。这个过程称为类模板实例化。
类模板的成员函数
成员函数定义
类模板的成员函数可以在类内定义,也可以在类外定义。在类外定义时,需要使用模板前缀和类限定符:
// 类内定义(如前面例子中的size()函数)
template <typename T>
class MyContainer {
public:
size_t size() const { return count; } // 直接在类内定义
// ...其他声明
};
// 类外定义
template <typename T>
MyContainer<T>::MyContainer(size_t initialCapacity) :
elements(new T[initialCapacity]),
count(0),
capacity(initialCapacity) {
}
template <typename T>
MyContainer<T>::~MyContainer() {
delete[] elements;
}
template <typename T>
void MyContainer<T>::push_back(const T& element) {
if (count == capacity) {
// 扩展容量
capacity *= 2;
T* newElements = new T[capacity];
for (size_t i = 0; i < count; ++i) {
newElements[i] = elements[i];
}
delete[] elements;
elements = newElements;
}
elements[count++] = element;
}
template <typename T>
T& MyContainer<T>::at(size_t index) {
if (index >= count) {
throw std::out_of_range("Index out of bounds");
}
return elements[index];
}
template <typename T>
const T& MyContainer<T>::at(size_t index) const {
if (index >= count) {
throw std::out_of_range("Index out of bounds");
}
return elements[index];
}
在类外定义成员函数时,每个函数定义前都必须有完整的模板声明,并且在函数名前使用MyContainer<T>::
限定符。
成员函数模板
类模板的成员函数本身也可以是模板,这称为成员函数模板:
template <typename T>
class MyContainer {
// ...其他成员
public:
// 成员函数模板
template <typename Func>
void forEach(Func func) {
for (size_t i = 0; i < count; ++i) {
func(elements[i]);
}
}
// 另一个成员函数模板
template <typename U>
void appendFrom(const MyContainer<U>& other) {
for (size_t i = 0; i < other.size(); ++i) {
// 需要U能转换为T
push_back(static_cast<T>(other.at(i)));
}
}
};
使用成员函数模板:
int main() {
MyContainer<int> intContainer;
intContainer.push_back(1);
intContainer.push_back(2);
intContainer.push_back(3);
// 使用forEach和lambda表达式打印元素
intContainer.forEach([](const int& value) {
std::cout << value << " ";
});
std::cout << std::endl;
// 从double容器向int容器添加元素
MyContainer<double> doubleContainer;
doubleContainer.push_back(1.1);
doubleContainer.push_back(2.2);
intContainer.appendFrom(doubleContainer); // 将double转换为int
return 0;
}
类模板的特化
类模板可以通过特化为特定类型提供专门的实现。这在类型有特殊需求或可以优化时非常有用。
全特化
类模板的全特化为特定类型提供完全不同的实现:
// 主模板
template <typename T>
class Storage {
private:
T data;
public:
Storage(const T& value) : data(value) {}
void print() const {
std::cout << "Generic Storage: " << data << std::endl;
}
T& getData() { return data; }
};
// 为bool类型的全特化
template <>
class Storage<bool> {
private:
unsigned char data; // 使用一个字节存储多个bool值
static const unsigned char mask = 1; // 掩码
public:
Storage(bool value) : data(value ? mask : 0) {}
void print() const {
std::cout << "Bool Storage: " << (data ? "true" : "false") << std::endl;
}
bool getData() { return data & mask; }
void setData(bool value) {
if (value)
data |= mask; // 设置位
else
data &= ~mask; // 清除位
}
};
使用特化的类模板:
int main() {
Storage<int> intStorage(42);
intStorage.print(); // 使用通用版本
Storage<bool> boolStorage(true);
boolStorage.print(); // 使用bool特化版本
boolStorage.setData(false);
std::cout << "Changed value: " << (boolStorage.getData() ? "true" : "false") << std::endl;
return 0;
}
偏特化
类模板的偏特化允许为模板参数的某个子集提供特化:
// 主模板
template <typename T, typename U>
class Pair {
private:
T first;
U second;
public:
Pair(const T& t, const U& u) : first(t), second(u) {}
void print() const {
std::cout << "Generic Pair: " << first << ", " << second << std::endl;
}
};
// 两个类型相同的偏特化
template <typename T>
class Pair<T, T> {
private:
T first;
T second;
public:
Pair(const T& t1, const T& t2) : first(t1), second(t2) {}
void print() const {
std::cout << "Same-type Pair: " << first << ", " << second << std::endl;
}
bool areEqual() const { return first == second; }
};
// 指针类型的偏特化
template <typename T, typename U>
class Pair<T*, U*> {
private:
T* first;
U* second;
public:
Pair(T* t, U* u) : first(t), second(u) {}
void print() const {
std::cout << "Pointer Pair: " << *first << ", " << *second << std::endl;
}
};
使用偏特化的类模板:
int main() {
// 使用通用版本
Pair<int, double> p1(42, 3.14);
p1.print();
// 使用相同类型的偏特化
Pair<int, int> p2(10, 20);
p2.print();
std::cout << "Are equal: " << (p2.areEqual() ? "yes" : "no") << std::endl;
// 使用指针类型的偏特化
int x = 100, y = 200;
Pair<int, int> p3(&x, &y);
p3.print();
return 0;
}
默认模板参数
类模板可以为模板参数指定默认值,简化模板的使用:
template <typename T,
typename Container = std::vector<T>,
typename Compare = std::less<T>>
class PriorityQueue {
private:
Container data;
Compare comp;
public:
PriorityQueue() = default;
void push(const T& value) {
data.push_back(value);
std::push_heap(data.begin(), data.end(), comp);
}
T pop() {
T top = data.front();
std::pop_heap(data.begin(), data.end(), comp);
data.pop_back();
return top;
}
const T& top() const {
return data.front();
}
bool empty() const {
return data.empty();
}
size_t size() const {
return data.size();
}
};
使用默认模板参数:
int main() {
// 使用默认参数:vector<int>容器和less<int>比较器
PriorityQueue<int> minHeap;
minHeap.push(3);
minHeap.push(1);
minHeap.push(4);
while (!minHeap.empty()) {
std::cout << minHeap.pop() << " "; // 输出:1 3 4
}
std::cout << std::endl;
// 使用自定义比较器:最大堆
PriorityQueue<int, std::vector<int>, std::greater<int>> maxHeap;
maxHeap.push(3);
maxHeap.push(1);
maxHeap.push(4);
while (!maxHeap.empty()) {
std::cout << maxHeap.pop() << " "; // 输出:4 3 1
}
std::cout << std::endl;
return 0;
}
模板的继承与组合
模板类的继承
模板类可以继承自非模板类、其他模板类或者自身的特化:
// 基类模板
template <typename T>
class Container {
protected:
T* data;
size_t size;
public:
Container(size_t s = 0) : size(s), data(s > 0 ? new T[s] : nullptr) {}
virtual ~Container() { delete[] data; }
size_t getSize() const { return size; }
virtual void print() const = 0;
};
// 派生类模板从基类模板继承
template <typename T>
class Array : public Container<T> {
public:
Array(size_t s) : Container<T>(s) {}
T& operator[](size_t index) {
if (index >= this->size) throw std::out_of_range("Index out of bounds");
return this->data[index];
}
void print() const override {
std::cout << "Array: ";
for (size_t i = 0; i < this->size; ++i) {
std::cout << this->data[i] << " ";
}
std::cout << std::endl;
}
};
// 模板类派生自非模板类
class Shape {
protected:
std::string name;
public:
Shape(const std::string& n) : name(n) {}
virtual ~Shape() = default;
virtual double area() const = 0;
const std::string& getName() const { return name; }
};
template <typename T>
class Circle : public Shape {
private:
T radius;
public:
Circle(const std::string& name, T r) : Shape(name), radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
};
使用继承的模板类:
int main() {
// Array继承自Container
Array<int> intArray(5);
for (size_t i = 0; i < intArray.getSize(); ++i) {
intArray[i] = i * 10;
}
intArray.print();
// Circle继承自Shape
Circle<double> circle("My Circle", 2.5);
std::cout << circle.getName() << " has area: " << circle.area() << std::endl;
return 0;
}
模板与组合
模板类也经常使用组合(而非继承)来实现代码复用:
// 使用组合的Stack模板
template <typename T, typename Container = std::vector<T>>
class Stack {
private:
Container container; // 组合一个容器
public:
void push(const T& value) {
container.push_back(value);
}
void pop() {
if (empty()) throw std::underflow_error("Stack is empty");
container.pop_back();
}
const T& top() const {
if (empty()) throw std::underflow_error("Stack is empty");
return container.back();
}
bool empty() const {
return container.empty();
}
size_t size() const {
return container.size();
}
};
使用组合的模板类:
int main() {
// 默认使用vector作为底层容器
Stack<int> stack1;
stack1.push(1);
stack1.push(2);
stack1.push(3);
std::cout << "Stack with vector: ";
while (!stack1.empty()) {
std::cout << stack1.top() << " ";
stack1.pop();
}
std::cout << std::endl;
// 使用deque作为底层容器
Stack<int, std::deque<int>> stack2;
stack2.push(4);
stack2.push(5);
stack2.push(6);
std::cout << "Stack with deque: ";
while (!stack2.empty()) {
std::cout << stack2.top() << " ";
stack2.pop();
}
std::cout << std::endl;
return 0;
}
类模板的嵌套
类模板可以包含嵌套的类、结构体或者枚举,这些嵌套类型也可以依赖外部模板参数:
template <typename T>
class OuterTemplate {
public:
// 嵌套的类模板
template <typename U>
class NestedTemplate {
private:
T outerValue;
U innerValue;
public:
NestedTemplate(const T& t, const U& u) : outerValue(t), innerValue(u) {}
void print() const {
std::cout << "Outer value: " << outerValue
<< ", Inner value: " << innerValue << std::endl;
}
};
// 依赖于T的嵌套类
class NestedClass {
private:
T value;
public:
NestedClass(const T& v) : value(v) {}
void print() const {
std::cout << "Nested value: " << value << std::endl;
}
};
// 嵌套枚举(C++11起可以指定底层类型)
enum class Status : int {
Success = 0,
Failure = 1,
Pending = 2
};
};
使用嵌套类模板:
int main() {
// 使用嵌套的类模板
OuterTemplate<int>::NestedTemplate<std::string> nested(42, "Hello");
nested.print();
// 使用依赖于外部模板参数的嵌套类
OuterTemplate<double>::NestedClass nestedObj(3.14);
nestedObj.print();
// 使用嵌套枚举
auto status = OuterTemplate<int>::Status::Success;
std::cout << "Status value: " << static_cast<int>(status) << std::endl;
return 0;
}
类型萃取和SFINAE
类模板在实现通用代码时,常常需要对不同类型做特殊处理。类型萃取(Type Traits)和SFINAE(Substitution Failure Is Not An Error)是用于此目的的重要技术。
类型萃取
类型萃取允许我们在编译时检查和修改类型特性:
#include <type_traits>
// 使用类型萃取实现通用容器
template <typename T>
class SafeContainer {
private:
std::vector<T> data;
// 使用SFINAE选择合适的初始化函数
template <typename U = T>
typename std::enable_if<std::is_default_constructible<U>::value>::type
initialize(size_t size) {
data.resize(size); // T可以默认构造,直接调用resize
}
template <typename U = T>
typename std::enable_if<!std::is_default_constructible<U>::value>::type
initialize(size_t size) {
// T不可默认构造,不调整大小,仅预留空间
data.reserve(size);
}
public:
SafeContainer(size_t size = 0) {
initialize<T>(size);
}
void add(const T& value) {
data.push_back(value);
}
size_t size() const {
return data.size();
}
// 基于类型特性提供特定功能
template <typename U = T>
typename std::enable_if<std::is_arithmetic<U>::value, U>::type
sum() const {
U result = U();
for (const auto& item : data) {
result += item;
}
return result;
}
};
使用结合类型萃取的容器:
class NonDefaultConstructible {
private:
int value;
public:
NonDefaultConstructible(int v) : value(v) {}
// 没有默认构造函数
};
int main() {
// 使用可默认构造的类型
SafeContainer<int> intContainer(5);
intContainer.add(10);
intContainer.add(20);
std::cout << "Int container size: " << intContainer.size() << std::endl;
std::cout << "Int container sum: " << intContainer.sum() << std::endl;
// 使用不可默认构造的类型
SafeContainer<NonDefaultConstructible> customContainer;
customContainer.add(NonDefaultConstructible(5));
customContainer.add(NonDefaultConstructible(10));
std::cout << "Custom container size: " << customContainer.size() << std::endl;
// customContainer.sum() 会导致编译错误,因为NonDefaultConstructible不是算术类型
return 0;
}
SFINAE技术
SFINAE允许我们根据类型特性提供不同的模板特化:
#include <iostream>
#include <type_traits>
#include <vector>
#include <list>
// 检测容器是否有随机访问迭代器
template <typename Container>
class ContainerTraits {
private:
// 测试函数,只有当迭代器为随机访问迭代器时有效
template <typename C>
static constexpr auto test(int)
-> decltype(
typename C::iterator() += 1, // 随机访问迭代器支持+=操作
std::true_type{}
);
// 匹配任意类型的后备函数
template <typename>
static constexpr std::false_type test(...);
public:
// value为true表示容器有随机访问迭代器
static constexpr bool has_random_access = decltype(test<Container>(0))::value;
};
// 根据容器特性选择最优的算法
template <typename Container>
void process(Container& container) {
if constexpr (ContainerTraits<Container>::has_random_access) {
std::cout << "Using fast algorithm for random access container" << std::endl;
// 实现依赖随机访问的快速算法
} else {
std::cout << "Using general algorithm for sequential access container" << std::endl;
// 实现适用于顺序访问的通用算法
}
}
使用SFINAE的容器处理:
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> lst = {1, 2, 3, 4, 5};
std::cout << "Vector has random access: "
<< (ContainerTraits<std::vector<int>>::has_random_access ? "yes" : "no") << std::endl;
std::cout << "List has random access: "
<< (ContainerTraits<std::list<int>>::has_random_access ? "yes" : "no") << std::endl;
process(vec); // 使用快速算法
process(lst); // 使用一般算法
return 0;
}
可变参数模板类
C++11引入了可变参数模板,允许接受任意数量和类型的模板参数:
// 可变参数类模板
template <typename... Types>
class Tuple;
// 基本情况:空元组
template <>
class Tuple<> {
public:
static constexpr size_t size = 0;
void print() const {
std::cout << "()" << std::endl;
}
};
// 递归情况:首元素 + 剩余元素元组
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
private:
Head head;
// 基类类型别名
using Base = Tuple<Tail...>;
public:
static constexpr size_t size = 1 + Base::size;
// 构造函数
Tuple(const Head& h, const Tail&... tail)
: Base(tail...), head(h) {}
// 获取首元素
const Head& getHead() const {
return head;
}
// 获取尾元组
const Base& getTail() const {
return *this;
}
// 递归打印元组内容
void print() const {
std::cout << "(" << head;
printRest();
std::cout << ")" << std::endl;
}
private:
void printRest() const {
const Base& tail = getTail();
// 检查尾元组是否为空
if constexpr (Base::size > 0) {
std::cout << ", ";
// 访问尾元组的getHead()方法
std::cout << tail.getHead();
// 递归处理剩余元素
tail.printRest();
}
}
};
使用可变参数类模板:
int main() {
// 创建不同类型的元组
Tuple<int, double, std::string> t1(42, 3.14, "hello");
std::cout << "Tuple t1: ";
t1.print();
std::cout << "Size of t1: " << decltype(t1)::size << std::endl;
// 创建单元素元组
Tuple<char> t2('A');
std::cout << "Tuple t2: ";
t2.print();
// 创建空元组
Tuple<> t3;
std::cout << "Tuple t3: ";
t3.print();
return 0;
}
类模板实例化控制
类模板的每次实例化都会生成代码,这可能导致代码膨胀。通过显式实例化和外部模板声明,可以控制实例化的位置和次数:
// 在头文件中声明模板
// mycontainer.h
template <typename T>
class MyContainer {
private:
std::vector<T> data;
public:
void add(const T& value);
const T& get(size_t index) const;
size_t size() const;
};
// 实现模板
// mycontainer.cpp
template <typename T>
void MyContainer<T>::add(const T& value) {
data.push_back(value);
}
template <typename T>
const T& MyContainer<T>::get(size_t index) const {
return data.at(index);
}
template <typename T>
size_t MyContainer<T>::size() const {
return data.size();
}
// 显式实例化常用类型,减少代码重复
template class MyContainer<int>;
template class MyContainer<double>;
template class MyContainer<std::string>;
// 在其他文件中使用外部模板声明
// main.cpp
extern template class MyContainer<int>;
extern template class MyContainer<double>;
int main() {
MyContainer<int> intContainer;
intContainer.add(42);
MyContainer<double> doubleContainer;
doubleContainer.add(3.14);
// 这将导致新的实例化,因为没有显式实例化
MyContainer<char> charContainer;
charContainer.add('A');
return 0;
}
实际应用案例
让我们看一些类模板的实际应用案例:
通用智能指针
实现一个简单的智能指针模板类:
#include <iostream>
#include <cstddef>
// 定义删除器类型
template <typename T>
struct DefaultDeleter {
void operator()(T* ptr) const {
delete ptr;
}
};
template <typename T>
struct ArrayDeleter {
void operator()(T* ptr) const {
delete[] ptr;
}
};
// 自定义智能指针模板
template <typename T, typename Deleter = DefaultDeleter<T>>
class UniquePtr {
private:
T* ptr;
Deleter deleter;
// 禁止拷贝
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
public:
// 构造函数
explicit UniquePtr(T* p = nullptr, Deleter d = Deleter())
: ptr(p), deleter(std::move(d)) {}
// 移动构造函数
UniquePtr(UniquePtr&& other) noexcept
: ptr(other.ptr), deleter(std::move(other.deleter)) {
other.ptr = nullptr;
}
// 移动赋值运算符
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
reset();
ptr = other.ptr;
deleter = std::move(other.deleter);
other.ptr = nullptr;
}
return *this;
}
// 析构函数
~UniquePtr() {
reset();
}
// 释放资源
void reset(T* p = nullptr) {
if (ptr != nullptr) {
deleter(ptr);
}
ptr = p;
}
// 放弃所有权
T* release() {
T* temp = ptr;
ptr = nullptr;
return temp;
}
// 访问指针
T* get() const {
return ptr;
}
// 解引用操作符
T& operator*() const {
return *ptr;
}
// 成员访问操作符
T* operator->() const {
return ptr;
}
// 布尔转换
explicit operator bool() const {
return ptr != nullptr;
}
// 交换
void swap(UniquePtr& other) noexcept {
std::swap(ptr, other.ptr);
std::swap(deleter, other.deleter);
}
};
// 为数组类型特化make_unique
template <typename T, typename... Args>
UniquePtr<T> make_unique(Args&&... args) {
return UniquePtr<T>(new T(std::forward<Args>(args)...));
}
template <typename T>
UniquePtr<T, ArrayDeleter<T>> make_unique_array(size_t size) {
return UniquePtr<T, ArrayDeleter<T>>(new T[size]);
}
使用自定义智能指针:
class Resource {
private:
std::string name;
public:
Resource(const std::string& n = "Unnamed") : name(n) {
std::cout << "Resource " << name << " created" << std::endl;
}
~Resource() {
std::cout << "Resource " << name << " destroyed" << std::endl;
}
void use() const {
std::cout << "Using resource " << name << std::endl;
}
};
int main() {
// 创建智能指针
UniquePtr<Resource> res1(new Resource("First"));
// 使用make_unique
auto res2 = make_unique<Resource>("Second");
// 使用资源
res1->use();
(*res2).use();
// 移动所有权
UniquePtr<Resource> res3 = std::move(res1);
// res1现在为空
if (!res1) {
std::cout << "res1 is empty" << std::endl;
}
// res3持有原res1的资源
if (res3) {
res3->use();
}
// 数组版本
auto numbers = make_unique_array<int>(5);
// 函数结束时,所有智能指针销毁,释放资源
return 0;
}
泛型事件系统
实现一个简单的类型安全事件系统:
#include <iostream>
#include <string>
#include <functional>
#include <unordered_map>
#include <vector>
#include <memory>
#include <any>
// 事件基类
class EventBase {};
// 泛型事件类
template <typename... Args>
class Event : public EventBase {
public:
using HandlerFunc = std::function<void(Args...)>;
using HandlerId = size_t;
private:
std::unordered_map<HandlerId, HandlerFunc> handlers;
HandlerId nextId = 0;
public:
// 注册处理器
HandlerId addHandler(HandlerFunc handler) {
HandlerId id = nextId++;
handlers[id] = std::move(handler);
return id;
}
// 移除处理器
void removeHandler(HandlerId id) {
handlers.erase(id);
}
// 触发事件
void trigger(Args... args) const {
for (const auto& [id, handler] : handlers) {
handler(args...);
}
}
// 获取处理器数量
size_t handlerCount() const {
return handlers.size();
}
};
// 事件分发器
class EventDispatcher {
private:
std::unordered_map<std::string, std::shared_ptr<EventBase>> events;
public:
// 获取事件(如果不存在则创建)
template <typename... Args>
Event<Args...>& getEvent(const std::string& name) {
auto it = events.find(name);
if (it == events.end()) {
auto event = std::make_shared<Event<Args...>>();
events[name] = event;
return *event;
}
// 尝试将事件转换为正确的类型
auto* typedEvent = dynamic_cast<Event<Args...>*>(it->second.get());
if (!typedEvent) {
throw std::runtime_error("Event type mismatch");
}
return *typedEvent;
}
// 触发事件
template <typename... Args>
void dispatchEvent(const std::string& name, Args... args) {
auto& event = getEvent<Args...>(name);
event.trigger(args...);
}
};
使用泛型事件系统:
class Player {
private:
std::string name;
int health;
public:
Player(const std::string& n, int h) : name(n), health(h) {}
void takeDamage(int amount) {
health -= amount;
std::cout << name << " takes " << amount << " damage. Health: " << health << std::endl;
}
void heal(int amount) {
health += amount;
std::cout << name << " heals for " << amount << ". Health: " << health << std::endl;
}
const std::string& getName() const { return name; }
int getHealth() const { return health; }
};
int main() {
EventDispatcher dispatcher;
// 定义并注册各种事件
using DamageEvent = Event<Player&, int>;
using HealEvent = Event<Player&, int>;
using GameOverEvent = Event<const std::string&>;
// 创建玩家
Player player("Hero", 100);
// 注册伤害事件处理器
dispatcher.getEvent<Player&, int>("damage").addHandler(
[](Player& p, int amount) {
p.takeDamage(amount);
std::cout << "Damage event handled!" << std::endl;
}
);
// 注册治疗事件处理器
dispatcher.getEvent<Player&, int>("heal").addHandler(
[](Player& p, int amount) {
p.heal(amount);
std::cout << "Heal event handled!" << std::endl;
}
);
// 注册游戏结束事件处理器
dispatcher.getEvent<const std::string&>("gameOver").addHandler(
[](const std::string& message) {
std::cout << "Game over: " << message << std::endl;
}
);
// 触发一些事件
dispatcher.dispatchEvent("damage", player, 30);
dispatcher.dispatchEvent("heal", player, 15);
dispatcher.dispatchEvent("damage", player, 95);
// 检查玩家状态
if (player.getHealth() <= 0) {
dispatcher.dispatchEvent("gameOver", "Player has been defeated!");
}
return 0;
}
类模板的最佳实践
接口设计
- 简洁明了的模板参数:只使用必要的模板参数,并为常用场景提供默认参数。
- 分离接口与实现:将模板的声明和实现分开,提高代码可读性。
- 提供清晰的文档:注明模板参数的预期类型和约束条件。
// 良好设计的模板类
template <typename T,
typename Allocator = std::allocator<T>,
typename Compare = std::less<T>>
class SortedContainer {
public:
// 类型别名提高可读性
using value_type = T;
using allocator_type = Allocator;
using compare_type = Compare;
using size_type = std::size_t;
// 构造函数
explicit SortedContainer(const Compare& comp = Compare{});
// 主要接口方法
void insert(const T& value);
bool contains(const T& value) const;
size_type size() const;
bool empty() const;
// 迭代器
using iterator = /* ... */;
using const_iterator = /* ... */;
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
const_iterator cbegin() const;
const_iterator cend() const;
// 对于每个必要的操作都提供文档
/**
* 插入值并保持容器排序
* @param value 要插入的值
* @complexity O(log n) for lookup, O(n) for insertion
*/
void insert(const T& value);
// ... 其他实现 ...
};
模板约束
使用适当的技术确保模板参数满足必要的要求:
// 使用C++20概念
template <typename T>
concept Sortable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
{ a = b } -> std::same_as<T&>;
};
template <Sortable T>
class SortedVector {
// 实现...
};
// 在C++17及更早版本中使用SFINAE
template <typename T,
typename = std::enable_if_t<
std::is_copy_assignable_v<T> &&
std::is_copy_constructible_v<T> &&
std::is_default_constructible_v<T>
>>
class SafeContainer {
// 实现...
};
避免常见问题
- 过多的模板参数:限制模板参数的数量,使类易于使用。
- 不必要的依赖:避免在模板中包含不依赖于模板参数的代码。
- 编译时间膨胀:使用技术如显式实例化和外部模板声明减少代码重复。
// 不好的设计:过多的模板参数
template <typename T,
typename U,
typename V,
typename W,
typename Allocator = std::allocator<T>>
class OverTemplated { /* ... */ };
// 更好的设计:使用嵌套或组合
template <typename Key, typename Value>
class Dictionary {
private:
struct Entry {
Key key;
Value value;
};
std::vector<Entry> entries;
public:
// 更简洁的接口
};
// 不好的设计:不依赖模板参数的代码
template <typename T>
class Logger {
public:
void log(const std::string& message) {
// 这个方法不使用T
std::cout << message << std::endl;
}
void logValue(const T& value) {
std::cout << value << std::endl;
}
};
// 更好的设计:分离不依赖模板参数的代码
class LoggerBase {
public:
void log(const std::string& message) {
std::cout << message << std::endl;
}
};
template <typename T>
class TypedLogger : public LoggerBase {
public:
void logValue(const T& value) {
std::cout << value << std::endl;
}
};
总结
类模板是C++泛型编程的核心组件之一,它允许我们创建能够处理各种数据类型的通用类。通过类模板,我们可以实现代码复用、类型安全和高效的容器和算法。
本文中,我们探讨了:
- 类模板的基本语法:定义和实例化类模板的方法
- 类模板的成员函数:在类内和类外定义成员函数
- 类模板的特化:为特定类型提供专门实现的全特化和偏特化
- 默认模板参数:简化模板使用的方法
- 模板的继承与组合:通过继承和组合使用模板
- 类模板的嵌套:在模板中定义嵌套类型
- 类型萃取和SFINAE:根据类型特性提供不同实现
- 可变参数模板类:创建接受任意数量参数的模板
- 类模板实例化控制:管理模板代码膨胀的技术
- 实际应用案例:智能指针和事件系统实现
- 最佳实践:设计良好模板类的指导原则
掌握类模板是成为高级C++程序员的关键一步。随着我们继续探索模板编程,接下来我们将深入学习模板特化技术,这将使我们能够为特定类型提供更加高效或专门化的实现。
参考资源
- C++ Reference
- 《C++ Templates: The Complete Guide》by David Vandevoorde and Nicolai M. Josuttis
- 《Modern C++ Design》by Andrei Alexandrescu
- 《Effective Modern C++》by Scott Meyers
- 《C++ Core Guidelines》by Bjarne Stroustrup and Herb Sutter
这是我C++学习之旅系列的第三十三篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!