最近在重构一个餐厅管理系统时遇到了一个典型问题:两家使用不同数据结构存储菜单的餐厅合并后,如何统一处理菜单遍历?煎饼屋使用vector存储早餐菜单,而餐厅则用原生数组管理午餐菜单。这种差异导致遍历代码需要针对不同数据结构编写重复逻辑。
关键痛点:每当新增一种菜单存储方式,就需要新增对应的遍历代码,这直接违反了开闭原则。
在C++中,vector和数组的遍历方式看似相似,实则存在本质差异:
为PancakeHouseMenu实现迭代器相对简单,直接复用vector的迭代器即可:
cpp复制using iterator = vector<MenuItem>::iterator;
using const_iterator = vector<MenuItem>::const_iterator;
const_iterator begin() const { return menuItems.cbegin(); }
const_iterator end() const { return menuItems.cend(); }
但为DinerMenu实现自定义迭代器则需要完整定义迭代器类型:
cpp复制class ConstIterator {
// 必须定义的五种迭代器特性类型
using iterator_category = std::forward_iterator_tag;
using value_type = const MenuItem;
using pointer = const MenuItem*;
using reference = const MenuItem&;
using difference_type = std::ptrdiff_t;
const MenuItem* ptr;
public:
// 必须重载的运算符
reference operator*() const { return *ptr; }
pointer operator->() const { return ptr; }
ConstIterator& operator++() { ptr++; return *this; }
bool operator!=(const ConstIterator& other) const { /*...*/ }
};
通过统一迭代接口,Waitress类得到极大简化:
cpp复制template <class T>
void printMenu(const T& menu) {
for (const auto& item : menu) { // 统一使用范围for循环
cout << item.getName() << " - $" << item.getPrice() << endl;
}
}
这种实现带来三个显著好处:
iterator_category定义了迭代器的能力等级,影响算法选择:
input_iterator_tag:只读单向迭代器forward_iterator_tag:可读写单向迭代器bidirectional_iterator_tag:双向迭代器random_access_iterator_tag:随机访问迭代器我们的菜单迭代器只需forward_iterator_tag,因为仅需单向遍历。
良好的设计应区分读写迭代器:
cpp复制// 非const版本
Iterator begin() { return Iterator(&menuItems[0]); }
// const版本
ConstIterator begin() const {
return ConstIterator(&menuItems[0]);
}
实测发现自定义迭代器可能带来约5%的性能开销(基于10万次遍历测试),但换来的是:
在大多数场景下,这是值得的代价。
cpp复制auto it = menu.begin();
menu.addItem(...); // 可能导致vector扩容,迭代器失效
cout << *it; // 未定义行为!
现代C++提供了更优雅的解决方案:
cpp复制#include <ranges>
void printMenu(auto&& menu) {
for (const auto& item : menu | views::filter([](auto& i){
return i.isVegetarian();
})) {
// 打印素食选项
}
}
组合迭代器模式可实现深度遍历:
cpp复制class CompositeIterator {
stack<Iterator> stk;
public:
CompositeIterator(Iterator it) { stk.push(it); }
MenuItem& operator*() {
return *stk.top();
}
CompositeIterator& operator++() {
auto& current = *stk.top();
++stk.top();
if (current.hasChildren()) {
stk.push(current.begin());
}
while (!stk.empty() && stk.top() == stk.top().end()) {
stk.pop();
}
return *this;
}
};
API设计原则:
测试要点:
cpp复制TEST(MenuIterator, EmptyContainer) {
DinerMenu emptyMenu;
ASSERT_EQ(emptyMenu.begin(), emptyMenu.end());
}
TEST(MenuIterator, BoundaryCheck) {
PancakeHouseMenu menu;
auto it = menu.begin();
++it;
++it;
ASSERT_NE(it, menu.end());
++it;
ASSERT_EQ(it, menu.end());
}
在大型项目中,我们通过迭代器模式成功统一了17种不同数据结构的遍历接口,使相关代码行数减少40%,同时提高了扩展性。当需要新增Redis支持的菜单存储时,仅需实现对应的迭代器即可兼容现有系统。