16.8 为什么使用迭代器

到目前为止,我们已经看到了迭代器的机制,但是要理解为什么它们如此重要还需要采用更复杂的例子。

在一个真实的面向对象程序中,经常可以看到多态性、动态对象创建和容器在一起使用。容器和动态对象创建解决了不知道我们需要多少对象以及对象是什么类型的这样的问题。如果配置一个容器存放指向基类对象的指针,每次放置一个派生类指针进入容器时,就发生向上类型转换。正如本书第1卷中最后的代码,这个例子还将我们至今已经学习过的各个不同方面放在一起,如果我们能理解这个例子,我们就为学习第2卷做好了准备。

假设我们正在创建一个程序,这个程序允许用户编辑和产生不同种类的图画。每个图画都是一个包含一组Shape对象的对象。

16.8 为什么使用迭代器 - 图1

16.8 为什么使用迭代器 - 图2

这段代码使用了基类中虚函数的典型结构,这次虚函数在派生类中被重新定义。注意,Shape类包含一个虚析构函数,应当将有些东西自动添加到具有虚函数的任何类中。如果一个容器存放指向Shape对象的指针或引用,则当对这些对象调用这个虚析构函数时,所有的相关数据都将被正确地清除。

在下面例子中,每一个不同类型的图画都使用了不同种类的模板化容器类:已经在本章定义的PStash和Stack,以及来自标准C++库的vector类。容器的“使用”是极其简单的,并且通常情况下,继承可能不是最好的方法(组合可能更有意义),但是,在这种情况下,继承是一个简单的方法,并没有从这个例子中去掉。

16.8 为什么使用迭代器 - 图3

16.8 为什么使用迭代器 - 图4

不同类型的容器都存放指向Shape的指针和指向Shape派生类的向上类型转换对象的指针。然而,因为多态性,当调用虚函数时,仍然出现正确的行为。

注意,Shape*的数组sarray也可以被看做一个容器。

16.8.1 函数模板

在drawAll()中,我们已经看到了一些新东西。但是,到本章为止,我们仅仅使用了类模板,它们实例化基于一个或多个类型参数的新表。然而,我们可以同样容易地创建函数模板,它们创建基于类型参数的新函数。创建函数模板的理由与使用类模板的理由相同:我们试图创建一般性的代码,我们可以通过延迟规定一个或多个类型的方法来创建这样的代码。我们只想写明这些类型参数支持特定运算,并不确切地说明它们是什么类型。

函数模板drawAll()可以看做是一个算法(在标准C++库中大部分函数模板被称为算法)。它只是给出描述元素的一个区域的迭代器,说明如何做某件事情,只要这些迭代器能被反向引用、增加和比较。在本章中,我们已经开发出这种迭代器,但这也不是巧合,这种迭代器由标准C++库中的容器生成,在这个例子中由使用vector证实。

我们还希望drawAll()是一个泛型算法(generic algorithm),所以容器可以是任意类型的,我们没有必要为每个不同类型的容器编写这个算法的新版本。在此,函数模板是基本的,因为它们能自动地为每个不同类型的容器产生特殊代码。但是,如果没有由迭代器提供的另外的间接性,这种泛型(genericness)就没有可能。这就是迭代器为什么如此重要的原因;它们允许用户编写涉及容器的通用代码,而用户并不知道容器的下层结构。(注意,在C++中,为了正确工作,迭代器和泛型算法都需要函数模板。)

在main()中可以看到这点的证明,因为drawAll()的工作不随着容器类型的不同而改变。更有趣的是,drawAll()对于指向数组sarray的开始和结尾的指针也能工作。这种将数组作为容器处理的能力是标准C++库设计的一部分,它们的算法很像drawAll()。

因为容器类模板很少关系到普通类所具有的继承和向上类型转换,所以不会在容器类中看到虚函数。容器的重用是用模板,而不是用继承实现的。