精读C++20设计模式——行为型设计模式:迭代器模式
精读C++20设计模式——行为型设计模式:迭代器模式
前言
标准库就是再好不过的例子了,到处都是迭代器(憋笑),很显然,在组合模式的时候,我们经常有这样的需求:需要遍历一个集合(数组、链表、树、图、数据库结果集等),但又不希望暴露集合的内部表示或把遍历逻辑散落在调用方代码中。把遍历逻辑和集合实现耦合,会导致难以维护、难以替换底层数据结构,并破坏单一职责。这个时候使用迭代器,显然就是合适的。
什么是迭代器
迭代器模式(Iterator Pattern) 的核心思想是:为访问一个集合对象的元素提供一个顺序访问的方法,而不暴露该对象的内部表示。它把“如何遍历”从“被遍历对象”中抽离出来,提供统一的访问接口。C++ 标准库(std::vector::iterator
/ std::map::iterator
)就是迭代器模式的实际应用。
一个例子:二叉树的迭代遍历(中序)
#include <iostream>
#include <stack>
#include <memory>// 简单二叉树节点
template<typename T>
struct TreeNode {T val;TreeNode* left = nullptr;TreeNode* right = nullptr;TreeNode(T v): val(v) {}
};// 中序迭代器(外部迭代器)
template<typename T>
class InorderIterator {
public:using Node = TreeNode<T>;// 构造为从 root 的最左节点开始InorderIterator(Node* root) { pushLeft(root); }// 默认构造表示 end()InorderIterator() = default;T& operator*() { return stack_.top()->val; }// 前缀 ++InorderIterator& operator++() {Node* node = stack_.top();stack_.pop();if (node->right) pushLeft(node->right);return *this;}bool operator!=(const InorderIterator& other) const {return !(*this == other);}bool operator==(const InorderIterator& other) const {if (stack_.empty() && other.stack_.empty()) return true;if (stack_.empty() != other.stack_.empty()) return false;return stack_.top() == other.stack_.top();}private:std::stack<Node*> stack_;void pushLeft(Node* node) {while (node) {stack_.push(node);node = node->left;}}
};// 二叉树包装,提供 begin/end
template<typename T>
class BinaryTree {
public:using Node = TreeNode<T>;BinaryTree() : root_(nullptr) {}~BinaryTree() { /* 生产环境请实现 delete 逻辑或用 smart pointers */ }void setRoot(Node* r) { root_ = r; }InorderIterator<T> begin() { return InorderIterator<T>(root_); }InorderIterator<T> end() { return InorderIterator<T>(); }private:Node* root_;
};// 演示
int main() {// 构造一个测试树// 4// / \// 2 6// / \ / \// 1 3 5 7auto n1 = new TreeNode<int>(1);auto n3 = new TreeNode<int>(3);auto n5 = new TreeNode<int>(5);auto n7 = new TreeNode<int>(7);auto n2 = new TreeNode<int>(2); n2->left = n1; n2->right = n3;auto n6 = new TreeNode<int>(6); n6->left = n5; n6->right = n7;auto n4 = new TreeNode<int>(4); n4->left = n2; n4->right = n6;BinaryTree<int> tree;tree.setRoot(n4);for (auto it = tree.begin(); it != tree.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 简单清理(为示例目的)delete n1; delete n3; delete n5; delete n7; delete n2; delete n6; delete n4;return 0;
}
书中还提到了更牛的协程版本的调度,本人不太理解协程,但是为了尊重原书的内容,这里笔者没法做精读,只好照猫画虎了:
#include <coroutine> #include <exception> #include <iostream> #include <memory> #include <optional>// 简单 Generator<T>(教学用,非生产级) template<typename T> struct Generator {struct promise_type {std::optional<T> current_value;std::suspend_always yield_value(T value) {current_value = std::move(value);return {};}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }Generator get_return_object() {return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) };}void return_void() {}void unhandled_exception() { std::terminate(); }};using handle_type = std::coroutine_handle<promise_type>;explicit Generator(handle_type h) : coro(h) {}~Generator() { if (coro) coro.destroy(); }// 简单的迭代接口(仅演示)bool next() {if (!coro || coro.done()) return false;coro.resume();return !coro.done();}T getValue() {return *coro.promise().current_value;}private:handle_type coro; };// 二叉树节点(同上) template<typename T> struct Node {T val;Node* left = nullptr;Node* right = nullptr;Node(T v): val(v) {} };// 中序协程实现 template<typename T> Generator<T> inorder_generator(Node<T>* root) {if (!root) co_return;for (auto v : inorder_generator(root->left)) {co_yield v; // 先产出左子树}co_yield root->val; // 再产出根for (auto v : inorder_generator(root->right)) {co_yield v;} }// 但上面的 for-range 不能直接用,因为我们的 Generator 没有提供 range 支持。 // 这里改成直接递归的 yield(不使用 range-for) template<typename T> Generator<T> inorder_gen_simple(Node<T>* node) {if (!node) co_return;co_yield std::coroutine_handle<typename Generator<T>::promise_type>::from_promise(*(std::addressof(node->left), typename Generator<T>::promise_type())); // 这里仅为示意 —— 实际中需直接写递归 co_yield// 更清晰办法(直接递归写法):if (node->left) {auto g = inorder_gen_simple(node->left);// 手动迭代 g:需要更完整的 Generator 支持,这里不赘述}co_yield node->val;if (node->right) {auto g = inorder_gen_simple(node->right);// 手动迭代 g} }
扩展:常见的方案对比表(易读版)
方案 | 典型场景 | 优点 | 缺点 |
---|---|---|---|
外部迭代器(STL) | 内存集合、本地算法 | 灵活、并发迭代、与算法兼容 | 实现复杂、需要客户端管理 |
内部迭代器(forEach) | 简单遍历、封装操作 | API 简洁,集合控制遍历 | 不易暂停、组合性差 |
协程/生成器 | 递归遍历、按需序列 | 代码直观、惰性、易写复杂遍历 | 需协程支持/库、实现复杂 |
复合迭代器(树等) | 树/文件系统 | 扁平化复杂结构、可多策略 | 需额外栈/队列,较复杂 |
适配器(Filter/Map) | 流式处理 | 可组合、模块化 | 多层包装有性能影响 |
分片/并行迭代器 | 大数据/并行处理 | 并行加速 | 一致性/合并复杂 |
批量/分页迭代器 | 网络/数据库 | IO 高效、少往返 | 复杂的游标管理与一致性问题 |
总结
我们遇到什么问题?
当需要遍历不同类型的集合(数组、链表、树、图、数据库结果等)时,如何做到不暴露集合内部实现,同时为不同集合提供统一、可复用、可扩展的遍历接口?直接让客户端知道集合内部结构会导致高耦合、维护成本上升、难以复用。
迭代器模式如何解决?
- 把“访问集合元素”的责任从集合本身中抽离出来,提供一个统一的访问接口(迭代器)。
- 客户端依赖迭代器接口而不是集合的内部表示,从而解耦。
- 通过不同的迭代器实现,支持多种遍历策略(顺序、反向、按层、条件过滤、并行分片等)。
各变种优劣(要点回顾)
- 外部迭代器(STL):灵活、可与算法组合、支持并发迭代;但实现要求高、客户端需管理流程。
- 内部迭代器(forEach):API 简单,便于封装;但不利于复杂控制与暂停/恢复。
- 生成器/协程:代码直观且能很好表达递归遍历、懒加载;但依赖协程特性或第三方库,且实现细节复杂。
- 复合/树形迭代器:适合把层次结构“扁平化”输出;但状态管理(栈/队列)和内存细节需要处理。
- 适配器链(filter/map):非常适用于流式处理与组合,但包装层多会带来一定开销。
- 并行/分片/分页迭代器:适合大数据与网络场景,提高吞吐但复杂度高。)。您更偏好哪一种?