引言
在STL的设计中,对不同的容器(Container)进行某种处理往往使用的都是同一个函数,比如要将一个整型链表(std::list
)或者数组里的元素划分成大于100和小于等于100两部分,直接调用std::partition()
即可,非常方便。
迭代器
为了达到这样的效果,STL中所有容器都包含了迭代器(Iterator),都可以通过迭代器对其进行访问。正如《STL源码剖析》中所说的,迭代器就像胶水一样,把STL中的容器和算法粘合到一起。
但是针对不同容器进行处理,往往要根据容器自身数据结构的特点使用不同的算法才能使效率最大化,而STL中算法函数的传入参数都是容器的迭代器,那是不是每个算法的实现都要针对每一种容器的迭代器重载一遍呢?
当然不可能,如果是这样STL的代码量就远不止现在这么点了。为了方便算法实现,设计者们将容器抽象为几类:
- 可读,支持单向单趟访问
- 支持单向多趟访问
- 支持双向访问
- 支持随机访问
相应地,他们的迭代器也就分为:
- 输入迭代器
- 前向迭代器
- 双向迭代器
- 随机访问迭代器
上面每一个迭代器都在前一个的基础上支持更多的功能。
(当然实际上还有所谓的“输出迭代器”,这里不作讨论。)
那么问题来了,STL怎么根据不同的迭代器类型调用不同的函数的呢(即如何进行分发)?
最直接的做法是在每一个迭代器类中定义一个静态变量,保存其所属的迭代器类型。函数通过传入的迭代器访问该变量获知其类型,通过条件判断使用相应的算法来处理。
不考虑其它的副作用,实际上这么做是没有必要的,因为迭代器的类型在编译的时候就已经是确定了的,把编译期能做的事情推到运行期,这不是C++的风格。
下面看一下STL中是怎么处理这个问题的。
tag
对于STL中每一种迭代器,都有一个表明其所属类型的tag。这些tag的定义如下:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
可以很清楚地看到他们的继承关系