迭代器模式提供一种方法,顺序访问一个聚合对象中的各个元素,而不暴露该对象的内部表示。
1. 类和接口
Aggregate(聚合类):这是一个集合类,它提供了创建迭代器对象的方法(通常是CreateIterator()
或类似的命名)。在这个例子中,Aggregate
类有一个Createlterator()
方法,该方法返回一个新的迭代器实例。这个迭代器实例是基于当前Aggregate
对象创建的,以便能够遍历它的元素。注意,图片中的Createlterator()
方法名似乎有一个小错误,应该是CreateIterator()
,但我将遵循图片中的实际文本。
lterator(迭代器接口):这是一个定义迭代器行为的接口。它声明了用于遍历元素的方法,如First()
(定位到序列的第一个元素),Next()
(移动到序列中的下一个元素),IsDone()
(检查序列中是否还有更多的元素),以及CurrentItem()
(获取当前位置的元素)。在标准的命名习惯中,接口名通常是单数且以大写字母开头,但这里使用了lterator
,可能是一个打字错误,正确的可能是Iterator
。
Client(客户端类):这是使用迭代器遍历聚合类中的元素的类。它持有一个迭代器对象的引用,并通过这个迭代器来遍历聚合类中的元素。在这个例子中,Client
类有一个名为lterator
的私有成员变量(可能是Iterator
的一个实例,但这里使用了lterator
),并使用了迭代器的First()
、Next()
和IsDone()
方法来遍历元素。
2. 实现类
ConcreteAggregate(具体聚合类):这是Aggregate
类的一个具体实现,它实现了CreateIterator()
方法来返回一个Concretelterator
(应为ConcreteIterator
)实例。这个实例能够遍历ConcreteAggregate
对象中的元素。
Concretelterator(具体迭代器类):这是lterator
(应为Iterator
)接口的一个具体实现。它实现了接口中定义的所有方法,包括First()
、Next()
、IsDone()
和CurrentItem()
,以便能够遍历ConcreteAggregate
对象中的元素。在这个例子中,Concreteelterator
的构造函数接收一个ConcreteAggregate
对象作为参数,以便它能够知道要遍历哪个集合。注意,这里的Concretelterator
也是一个小错误,正确的应该是ConcreteIterator
。
lterator
应该是Iterator
,Createlterator()
应该是CreateIterator()
,Currentitem()
应该是CurrentItem()
。希望这个解释能够帮助您理解这张关于迭代器模式的结构图。
template<typename T>
class Iterator
{
public:
virtual void first() = 0;
virtual void next() = 0;
virtual bool isDone() const = 0;
virtual T& current() = 0;
};
template<typename T>
class MyCollection{
public:
Iterator<T> GetIterator(){
//...
}
};
template<typename T>
class CollectionIterator : public Iterator<T>{
MyCollection<T> mc;
public:
CollectionIterator(const MyCollection<T> & c): mc(c){ }
void first() override {
}
void next() override {
}
bool isDone() const override{
}
T& current() override{
}
};
void MyAlgorithm()
{
MyCollection<int> mc;
Iterator<int> iter= mc.GetIterator();
for (iter.first(); !iter.isDone(); iter.next()){
cout << iter.current() << endl;
}
}
这种迭代器实现方式存在一些问题,使得它在实际应用中可能并不是最佳选择。以下是一些关键问题及原因:
Iterator<int> iter = mc.GetIterator();
问题:Iterator
是一个抽象类,不能直接实例化。当你尝试返回一个Iterator
对象时,编译器会报错,因为不能创建一个抽象类的实例。
改进:应返回一个指向Iterator
的指针或者使用智能指针,例如std::unique_ptr
,以确保可以动态地分配一个具体的迭代器实例。
std::unique_ptr<Iterator<T>> GetIterator() {
return std::make_unique<CollectionIterator<T>>(*this);
}
Iterator<int> iter = mc.GetIterator();
问题:即使Iterator
能够返回一个对象(假设Iterator
不是抽象类),由于Iterator
的拷贝会发生对象切割问题,导致派生类的部分(CollectionIterator
的特定实现)丢失,剩下的只是一个基类的对象,这会使得运行时的多态行为失效。
改进:避免对象切割,返回迭代器的指针或引用。
问题:如果使用原始指针(如Iterator
)返回具体迭代器,会面临内存管理问题。如果用户忘记释放内存,可能会导致内存泄漏。
改进:使用智能指针来管理内存,例如std::unique_ptr
或std::shared_ptr
。智能指针会自动管理对象的生命周期,避免内存泄漏。
问题:CollectionIterator
持有MyCollection
的副本(通过成员变量mc
)。这意味着迭代器中保存的集合与原集合可能是两个不同的对象。如果集合发生改变,迭代器将无法反映这些变化。
改进:迭代器应持有集合的引用而不是副本,以确保它们始终引用相同的集合对象。
template<typename T>
class CollectionIterator : public Iterator<T> {
const MyCollection<T>& mc;
public:
CollectionIterator(const MyCollection<T>& c) : mc(c) { }
// 实现迭代器方法
};
问题:迭代器接口设计可能存在一些细节问题。例如,current()
方法返回对当前元素的引用时,如果集合为空,调用current()
会导致未定义行为。
改进:在实现current()
时,应该确保集合非空或抛出异常以避免未定义行为。也可以提供一个安全的接口,如返回指向元素的指针。
问题:GetIterator()
方法在你的设计中返回Iterator
,但是实际应该返回指向具体Iterator
的指针或引用,以支持多态。
改进:改用返回智能指针或指针以支持多态:
std::unique_ptr<Iterator<T>> GetIterator() {
return std::make_unique<CollectionIterator<T>>(*this);
}
以下是一个使用迭代器模式遍历一个简单集合(例如整数数组)的C++代码示例。
迭代器接口类:
template <typename T>
class Iterator {
public:
virtual ~Iterator() {}
virtual T first() = 0;
virtual T next() = 0;
virtual bool isDone() const = 0;
virtual T currentItem() const = 0;
};
具体迭代器类:
template <typename T>
class ConcreteIterator : public Iterator<T> {
private:
const std::vector<T>& collection;
size_t currentIndex;
public:
ConcreteIterator(const std::vector<T>& collection)
: collection(collection), currentIndex(0) {}
T first() override {
return collection[0];
}
T next() override {
currentIndex++;
if (currentIndex < collection.size()) {
return collection[currentIndex];
}
throw std::out_of_range("Iterator out of range");
}
bool isDone() const override {
return currentIndex >= collection.size();
}
T currentItem() const override {
if (currentIndex < collection.size()) {
return collection[currentIndex];
}
throw std::out_of_range("Iterator out of range");
}
};
聚合接口类:
template <typename T>
class Aggregate {
public:
virtual ~Aggregate() {}
virtual Iterator<T>* createIterator() const = 0;
};
具体聚合类:
template <typename T>
class ConcreteAggregate : public Aggregate<T> {
private:
std::vector<T> items;
public:
ConcreteAggregate(std::initializer_list<T> elements) : items(elements) {}
Iterator<T>* createIterator() const override {
return new ConcreteIterator<T>(items);
}
size_t size() const {
return items.size();
}
T getItem(size_t index) const {
if (index < items.size()) {
return items[index];
}
throw std::out_of_range("Index out of range");
}
};
客户端代码:
#include
#include
#include
int main() {
ConcreteAggregate<int> aggregate({1, 2, 3, 4, 5});
Iterator<int>* iterator = aggregate.createIterator();
for (iterator->first(); !iterator->isDone(); iterator->next()) {
std::cout << iterator->currentItem() << " ";
}
delete iterator;
return 0;
}
运行结果:
1 2 3 4 5
在 C++ 中,实现迭代器时选择使用模板而非多态性(即虚函数)主要有以下几个原因:
模板是在编译时进行类型替换的,这意味着模板代码在编译时生成特定类型的代码实例,而不需要在运行时进行类型检查或虚函数调用。这带来了以下几个性能上的好处:
无虚函数开销:使用模板可以避免虚函数调用的开销。虚函数调用通常涉及通过虚函数表(vtable)间接访问函数,可能导致一些额外的性能开销。而模板直接生成目标类型的代码,函数调用可以内联,从而提高运行时性能。
优化机会:编译器在模板实例化时有更多的优化机会。由于编译器知道确切的类型,可以进行更多的优化,比如内联、消除不必要的操作等。
强类型检查:模板提供了更强的类型安全性,因为所有类型信息在编译时就已经确定。模板迭代器在编译时会检查是否所有操作都符合类型约束,避免了在运行时发生类型错误。
支持更多容器:模板允许你为不同类型的集合(如std::vector
、std::list
等)生成不同的迭代器实现,而不需要定义多个派生类。这样,模板迭代器可以更好地适应各种容器类型,而不需要为每种类型手动定义派生类。
代码复用:模板允许编写泛型代码,这样可以避免重复为不同类型编写类似的代码。通过使用模板,可以编写一次泛型迭代器,然后在不同类型的集合上复用该迭代器代码。
泛型编程的自然适应性:模板是一种强大的工具,尤其适用于泛型编程。在 C++ 标准库中,几乎所有容器的迭代器都是通过模板实现的,形成了一个一致的泛型编程体系(如 STL)。这使得模板迭代器可以无缝地与 C++ 标准库的算法(如 std::sort
, std::find
等)协同工作。
尽管模板具有很多优势,但在某些情况下,多态性(即使用虚函数)仍然是合适的选择:
需要在运行时处理异构集合:如果需要在运行时处理不同类型的对象(如一个集合中包含不同类型的对象),模板可能无法提供直接的支持,此时使用多态性可以更好地处理这些需求。
接口稳定性:如果需要提供一个稳定的接口供外部使用,而不希望外部依赖模板实现(模板通常定义在头文件中,容易暴露实现细节),则可以选择使用虚函数接口来隐藏实现。
在 C++ 中,模板实现迭代器的方式由于性能、类型安全性、灵活性、和代码复用性等方面的优势,通常是优于使用多态性的。模板通过在编译时确定类型和生成代码,避免了运行时开销,使得迭代器更高效且灵活。但在处理需要运行时多态性或异构集合的情况下,多态性仍然是不可替代的。
优点:
缺点:
迭代器模式适用于以下场景: