C++学习:六个月从基础到就业——模板编程:类模板
C++学习:六个月从基础到就业——模板编程:类模板
本文是我C++学习之旅系列的第三十三篇技术文章,也是第二阶段"C++进阶特性"的第十一篇,主要介绍C++中的类模板编程。查看完整系列目录了解更多内容。
目录
- 引言
- 类模板的基本语法
- 类模板定义
- 类模板实例化
- 类模板的成员函数
- 成员函数定义
- 成员函数模板
- 类模板的特化
- 全特化
- 偏特化
- 默认模板参数
- 模板的继承与组合
- 模板类的继承
- 模板与组合
- 类模板的嵌套
- 类型萃取和SFINAE
- 类型萃取
- SFINAE技术
- 可变参数模板类
- 类模板实例化控制
- 实际应用案例
- 通用智能指针
- 泛型事件系统
- 类模板的最佳实践
- 接口设计
- 模板约束
- 避免常见问题
- 总结
- 参考资源
引言
在上一篇文章中,我们深入探讨了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能转换为Tpush_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转换为intreturn 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; // 设置位elsedata &= ~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继承自ContainerArray<int> intArray(5);for (size_t i = 0; i < intArray.getSize(); ++i) {intArray[i] = i * 10;}intArray.print();// Circle继承自ShapeCircle<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>::typeinitialize(size_t size) {data.resize(size); // T可以默认构造,直接调用resize}template <typename U = T>typename std::enable_if<!std::is_default_constructible<U>::value>::typeinitialize(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>::typesum() 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_uniqueauto 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) {// 这个方法不使用Tstd::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++学习之旅系列的第三十三篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!